Completed
Push — master ( 373634...d97c97 )
by Casey
02:52
created

RecordIterator::getClient()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * PHPOAIPMH Library
5
 *
6
 * @license http://opensource.org/licenses/MIT
7
 * @link https://github.com/caseyamcl/phpoaipmh
8
 * @version 2.0
9
 * @package caseyamcl/phpoaipmh
10
 * @author Casey McLaughlin <[email protected]>
11
 *
12
 * For the full copyright and license information, please view the LICENSE
13
 * file that was distributed with this source code.
14
 *
15
 * ------------------------------------------------------------------
16
 */
17
18
namespace Phpoaipmh;
19
20
use Phpoaipmh\Exception\BaseOaipmhException;
21
use Phpoaipmh\Exception\MalformedResponseException;
22
23
/**
24
 * Response List Entity iterates over records returned from an OAI-PMH Endpoint
25
 *
26
 * @author Casey McLaughlin <[email protected]>
27
 * @since v2.0
28
 */
29
class RecordIterator implements \Iterator, RecordIteratorInterface
30
{
31
    /**
32
     * @var Client
33
     */
34
    private $oaipmhClient;
35
36
    /**
37
     * @var string  The verb to use
38
     */
39
    private $verb;
40
41
    /**
42
     * @var array  OAI-PMH parameters passed as part of the request
43
     */
44
    private $params;
45
46
    /**
47
     * @var int  The number of total entities (if available)
48
     */
49
    private $totalRecordsInCollection;
50
51
    /**
52
     * @var \DateTimeInterface  RecordSet expiration date (if specified)
53
     */
54
    private $expireDate;
55
56
    /**
57
     * @var string  The resumption token
58
     */
59
    private $resumptionToken;
60
61
    /**
62
     * @var array  Array of records
63
     */
64
    private $batch;
65
66
    /**
67
     * @var int Total number of records processed
68
     */
69
    private $numProcessed = 0;
70
71
    /**
72
     * @var boolean  Total number of requests made
73
     */
74
    private $numRequests = 0;
75
76
    /**
77
     * @var \SimpleXMLElement|null  Used for tracking the iterator
78
     */
79
    private $currItem;
80
81
    /**
82
     * Constructor
83
     *
84
     * @param ClientInterface $client           The client to use
85
     * @param string          $verb             The verb to use when retrieving results from the client
86
     * @param array           $params           Optional parameters passed to OAI-PMH
87
     * @param string|null     $resumptionToken  Resumption token, if one exists
88
     */
89
    public function __construct(ClientInterface $client, $verb, array $params = array(), $resumptionToken = null)
90
    {
91
        //Set parameters
92
        $this->oaipmhClient     = $client;
0 ignored issues
show
Documentation Bug introduced by
$client is of type object<Phpoaipmh\ClientInterface>, but the property $oaipmhClient was declared to be of type object<Phpoaipmh\Client>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
93
        $this->verb             = $verb;
94
        $this->params           = $params;
95
        $this->resumptionToken  = $resumptionToken;
96
97
        //Node name error?
98
        if (! $this->getItemNodeName()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getItemNodeName() of type string|false is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
99
            throw new BaseOaipmhException('Cannot determine item name for verb: ' . $this->verb);
100
        }
101
    }
102
103
    /**
104
     * @return ClientInterface
105
     */
106
    public function getClient()
107
    {
108
        return $this->oaipmhClient;
109
    }
110
111
    /**
112
     * Get the total number of requests made during this run
113
     *
114
     * @return int The number of HTTP requests made
115
     */
116
    public function getNumRequests()
117
    {
118
        return $this->numRequests;
119
    }
120
121
    /**
122
     * Get the total number of records processed during this run
123
     *
124
     * @return int The number of records processed
125
     */
126
    public function getNumRetrieved()
127
    {
128
        return $this->numProcessed;
129
    }
130
131
132
    /**
133
     * Get the resumption token if it is specified
134
     *
135
     * @return null|string
136
     */
137
    public function getResumptionToken()
138
    {
139
        return $this->resumptionToken;
140
    }
141
142
    /**
143
     * @return \DateTimeInterface|null
144
     */
145
    public function getExpirationDate()
146
    {
147
        return $this->expireDate;
148
    }
149
150
    /**
151
     * Get the total number of records in the collection if available
152
     *
153
     * This only returns a value if the OAI-PMH server provides this information
154
     * in the response, which not all servers do (it is optional in the OAI-PMH spec)
155
     *
156
     * Also, the number of records may change during the requests, so it should
157
     * be treated as an estimate
158
     *
159
     * @return int|null
160
     * @deprecated Use `countTotalRecords()`
161
     */
162
    public function getTotalRecordsInCollection()
163
    {
164
        return $this->getTotalRecordCount();
165
    }
166
167
    /**
168
     * Get the total number of records in the collection if available
169
     *
170
     * This only returns a value if the OAI-PMH server provides this information
171
     * in the response, which not all servers do (it is optional in the OAI-PMH spec)
172
     *
173
     * Also, the number of records may change during the requests, so it should
174
     * be treated as an estimate
175
     *
176
     * @return int|null
177
     */
178
    public function getTotalRecordCount()
179
    {
180
        if ($this->currItem === null) {
181
            $this->next();
182
        }
183
184
        return $this->totalRecordsInCollection;
185
    }
186
187
    /**
188
     * Get the next item
189
     *
190
     * Return an item from the currently-retrieved batch, get next batch and
191
     * return first record from it, or return false if no more records
192
     *
193
     * @return \SimpleXMLElement|bool
194
     */
195
    public function nextItem()
196
    {
197
        if ($this->batch === null) {
198
            $this->batch = [];
199
        }
200
        
201
        //If no items in batch, and we have a resumptionToken or need to make initial request...
202
        if (count($this->batch) == 0 && ($this->resumptionToken or $this->numRequests == 0)) {
203
            $this->retrieveBatch();
204
        }
205
206
        //if still items in current batch, return one
207
        if (count($this->batch) > 0) {
208
            $this->numProcessed++;
209
210
            $item = array_shift($this->batch);
211
            $this->currItem = clone $item;
212
        } else {
213
            $this->currItem = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type object<SimpleXMLElement>|null of property $currItem.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
214
        }
215
216
        return $this->currItem;
217
    }
218
219
    /**
220
     * Do a request to get the next batch of items
221
     *
222
     * @return int The number of items in the batch after the retrieve
223
     */
224
    private function retrieveBatch()
225
    {
226
        // Set OAI-PMH parameters for request
227
        // If resumptionToken, then we ignore params and just use that
228
        $params = ($this->resumptionToken)
229
            ? ['resumptionToken' => $this->resumptionToken]
230
            : $this->params;
231
232
        // Node name and verb
233
        $nodeName = $this->getItemNodeName();
234
        $verb = $this->verb;
235
236
        //Do it..
237
        $resp = $this->oaipmhClient->request($verb, $params);
238
        $this->numRequests++;
239
240
        //Result format error?
241
        if (! isset($resp->$verb->$nodeName)) {
242
            throw new MalformedResponseException(sprintf("Expected XML element list '%s' missing for verb '%s'", $nodeName, $verb));
243
        }
244
245
        //Set the resumption token and expiration date, if specified in the response
246
        if (isset($resp->$verb->resumptionToken)) {
247
            $this->resumptionToken = (string) $resp->$verb->resumptionToken;
248
249
            if (isset($resp->$verb->resumptionToken['completeListSize'])) {
250
                $this->totalRecordsInCollection = (int) $resp->$verb->resumptionToken['completeListSize'];
251
            }
252
            if (isset($resp->$verb->resumptionToken['expirationDate'])) {
253
                $t = $resp->$verb->resumptionToken['expirationDate'];
254
                $this->expireDate = \DateTime::createFromFormat(\DateTime::ISO8601, $t);
0 ignored issues
show
Documentation Bug introduced by
It seems like \DateTime::createFromFor...\DateTime::ISO8601, $t) can also be of type false. However, the property $expireDate is declared as type object<DateTimeInterface>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
255
            }
256
        } else {
257
            //Unset the resumption token when we're at the end of the list
258
            $this->resumptionToken = null;
259
        }
260
261
        //Process the results
262
        foreach ($resp->$verb->$nodeName as $node) {
263
            $this->batch[] = $node;
264
        }
265
266
        //Return a count
267
        return count($this->batch);
268
    }
269
270
    /**
271
     * Get Item Node Name
272
     *
273
     * Map the item node name based on the verb
274
     *
275
     * @return string|boolean The element name for the mapping, or false if unmapped
276
     */
277
    private function getItemNodeName()
278
    {
279
        $mappings = array(
280
            'ListMetadataFormats' => 'metadataFormat',
281
            'ListSets'            => 'set',
282
            'ListIdentifiers'     => 'header',
283
            'ListRecords'         => 'record'
284
        );
285
286
        return (isset($mappings[$this->verb])) ? $mappings[$this->verb] : false;
287
    }
288
289
    // ----------------------------------------------------------------
290
    // Leaky abstraction methods
291
292
    /**
293
     * Get the current batch of records retrieved
294
     *
295
     * @return array|\SimpleXMLElement[]
296
     */
297
    public function getBatch()
298
    {
299
        return $this->batch;
300
    }
301
302
    /**
303
     * Reset the request state
304
     */
305
    public function reset()
306
    {
307
        $this->numRequests  = 0;
0 ignored issues
show
Documentation Bug introduced by
The property $numRequests was declared of type boolean, but 0 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
308
        $this->numProcessed = 0;
309
310
        $this->currItem                 = null;
311
        $this->resumptionToken          = null;
312
        $this->totalRecordsInCollection = null;
313
        $this->expireDate               = null;
314
315
        $this->batch = [];
316
    }
317
318
    // ----------------------------------------------------------------
319
    // Iterator methods
320
321
    public function current()
322
    {
323
        return ($this->currItem === null)
324
            ? $this->nextItem()
325
            : $this->currItem;
326
    }
327
328
    public function next()
329
    {
330
        return $this->nextItem();
331
    }
332
333
    public function key()
334
    {
335
        if ($this->currItem === null) {
336
            $this->nextItem();
337
        }
338
339
        return $this->getNumRetrieved();
340
    }
341
342
    public function valid()
343
    {
344
        return ($this->currItem !== false);
345
    }
346
347
    public function rewind()
348
    {
349
        $this->reset();
350
    }
351
}
352