Completed
Push — master ( 1c1d25...ad59d4 )
by Casey
01:22
created

RecordIterator::retrieveNextBatch()   B

Complexity

Conditions 9
Paths 42

Size

Total Lines 54

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 54
rs 7.448
c 0
b 0
f 0
cc 9
nc 42
nop 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * PHPOAIPMH Library
5
 *
6
 * @license http://opensource.org/licenses/MIT
7
 * @link https://github.com/caseyamcl/phpoaipmh
8
 * @version 3.0
9
 * @package caseyamcl/phpoaipmh
10
 * @author Casey McLaughlin <[email protected]>
11
 *
12
 * For the full copyright and license information, -please view the LICENSE.md
13
 * file that was distributed with this source code.
14
 *
15
 * ------------------------------------------------------------------
16
 */
17
18
namespace Phpoaipmh;
19
20
use DateTime;
21
use DateTimeInterface;
22
use Iterator;
23
use Phpoaipmh\Exception\BaseOaipmhException;
24
use Phpoaipmh\Exception\MalformedResponseException;
25
use SimpleXMLElement;
26
27
/**
28
 * Response List Entity iterates over records returned from an OAI-PMH Endpoint
29
 *
30
 * @author Casey McLaughlin <[email protected]>
31
 * @since v2.0
32
 */
33
class RecordIterator implements Iterator, RecordIteratorInterface
34
{
35
    /**
36
     * @var Client
37
     */
38
    private $oaipmhClient;
39
40
    /**
41
     * @var string  The verb to use
42
     */
43
    private $verb;
44
45
    /**
46
     * @var array  OAI-PMH parameters passed as part of the request
47
     */
48
    private $params;
49
50
    /**
51
     * @var int  The number of total entities (if available)
52
     */
53
    private $totalRecordsInCollection;
54
55
    /**
56
     * @var DateTimeInterface  RecordSet expiration date (if specified)
57
     */
58
    private $expireDate;
59
60
    /**
61
     * @var string  The resumption token
62
     */
63
    private $resumptionToken;
64
65
    /**
66
     * @var array  Array of records
67
     */
68
    private $batch;
69
70
    /**
71
     * @var int Total number of records processed
72
     */
73
    private $numProcessed = 0;
74
75
    /**
76
     * @var boolean  Total number of requests made
77
     */
78
    private $numRequests = 0;
79
80
    /**
81
     * @var SimpleXMLElement|null  Used for tracking the iterator
82
     */
83
    private $currItem;
84
85
    /**
86
     * Constructor
87
     *
88
     * @param ClientInterface $client           The client to use
89
     * @param string          $verb             The verb to use when retrieving results from the client
90
     * @param array           $params           Optional parameters passed to OAI-PMH
91
     * @param string|null     $resumptionToken  Resumption token, if one exists
92
     */
93
    public function __construct(ClientInterface $client, $verb, array $params = [], $resumptionToken = null)
94
    {
95
        //Set parameters
96
        $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...
97
        $this->verb             = $verb;
98
        $this->params           = $params;
99
        $this->resumptionToken  = $resumptionToken;
100
101
        //Node name error?
102
        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...
103
            throw new BaseOaipmhException('Cannot determine item name for verb: ' . $this->verb);
104
        }
105
    }
106
107
    /**
108
     * @return ClientInterface
109
     */
110
    public function getClient()
111
    {
112
        return $this->oaipmhClient;
113
    }
114
115
    /**
116
     * Get the total number of requests made during this run
117
     *
118
     * @return int The number of HTTP requests made
119
     */
120
    public function getNumRequests()
121
    {
122
        return $this->numRequests;
123
    }
124
125
    /**
126
     * Get the total number of records processed during this run
127
     *
128
     * @return int The number of records processed
129
     */
130
    public function getNumRetrieved()
131
    {
132
        return $this->numProcessed;
133
    }
134
135
136
    /**
137
     * Get the resumption token if it is specified
138
     *
139
     * @return null|string
140
     */
141
    public function getResumptionToken()
142
    {
143
        return $this->resumptionToken;
144
    }
145
146
    /**
147
     * @return DateTimeInterface|null
148
     */
149
    public function getExpirationDate()
150
    {
151
        return $this->expireDate;
152
    }
153
154
    /**
155
     * Get the total number of records in the collection if available
156
     *
157
     * This only returns a value if the OAI-PMH server provides this information
158
     * in the response, which not all servers do (it is optional in the OAI-PMH spec)
159
     *
160
     * Also, the number of records may change during the requests, so it should
161
     * be treated as an estimate
162
     *
163
     * @return int|null
164
     */
165
    public function getTotalRecordCount()
166
    {
167
        if ($this->currItem === null) {
168
            $this->next();
169
        }
170
171
        return $this->totalRecordsInCollection;
172
    }
173
174
    /**
175
     * Get the next item
176
     *
177
     * Return an item from the currently-retrieved batch, get next batch and
178
     * return first record from it, or return false if no more records
179
     *
180
     * @return SimpleXMLElement|bool
181
     */
182
    public function nextItem()
183
    {
184
        if ($this->batch === null) {
185
            $this->batch = [];
186
        }
187
        
188
        //If no items in batch, and we have a resumptionToken or need to make initial request...
189
        if (count($this->batch) == 0 && ($this->resumptionToken or $this->numRequests == 0)) {
190
            $this->retrieveNextBatch();
191
        }
192
193
        //if still items in current batch, return one
194
        if (count($this->batch) > 0) {
195
            $this->numProcessed++;
196
197
            $item = array_shift($this->batch);
198
            $this->currItem = clone $item;
199
        } else {
200
            $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...
201
        }
202
203
        return $this->currItem;
204
    }
205
206
    /**
207
     * Do a request to get the next batch of items
208
     *
209
     * @return int The number of items in the batch after the retrieve
210
     */
211
    public function retrieveNextBatch()
212
    {
213
        // Set OAI-PMH parameters for request
214
        // If resumptionToken, then we ignore params and just use that
215
        $params = ($this->resumptionToken)
216
            ? ['resumptionToken' => $this->resumptionToken]
217
            : $this->params;
218
219
        // Node name and verb
220
        $nodeName = $this->getItemNodeName();
221
        $verb = $this->verb;
222
223
        //Do it..
224
        $resp = $this->oaipmhClient->request($verb, $params);
225
        $this->numRequests++;
226
227
        //Result format error?
228
        if (! isset($resp->$verb->$nodeName)) {
229
            throw new MalformedResponseException(sprintf(
230
                "Expected XML element list '%s' missing for verb '%s'",
231
                $nodeName,
232
                $verb
233
            ));
234
        }
235
236
        //Set the resumption token and expiration date, if specified in the response
237
        if (isset($resp->$verb->resumptionToken)) {
238
            $this->resumptionToken = (string) $resp->$verb->resumptionToken;
239
240
            if (isset($resp->$verb->resumptionToken['completeListSize'])) {
241
                $this->totalRecordsInCollection = (int) $resp->$verb->resumptionToken['completeListSize'];
242
            }
243
            if (isset($resp->$verb->resumptionToken['expirationDate'])) {
244
                $t = $resp->$verb->resumptionToken['expirationDate'];
245
                $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...
246
            }
247
        } else {
248
            //Unset the resumption token when we're at the end of the list
249
            $this->resumptionToken = null;
250
        }
251
252
        //Process the results
253
        foreach ($resp->$verb->$nodeName as $node) {
254
            $this->batch[] = $node;
255
        }
256
257
        // If the entire set was sent in one request, store the total count.
258
        if ($this->numRequests == 1 && $this->resumptionToken === null) {
259
            $this->totalRecordsInCollection = count($this->batch);
260
        }
261
262
        //Return a count
263
        return count($this->batch);
264
    }
265
266
    /**
267
     * Get Item Node Name
268
     *
269
     * Map the item node name based on the verb
270
     *
271
     * @return string|boolean The element name for the mapping, or false if unmapped
272
     */
273
    private function getItemNodeName()
274
    {
275
        $mappings = array(
276
            'ListMetadataFormats' => 'metadataFormat',
277
            'ListSets'            => 'set',
278
            'ListIdentifiers'     => 'header',
279
            'ListRecords'         => 'record'
280
        );
281
282
        return (isset($mappings[$this->verb])) ? $mappings[$this->verb] : false;
283
    }
284
285
    // ----------------------------------------------------------------
286
    // Leaky abstraction methods
287
288
    /**
289
     * Get the current batch of records retrieved
290
     *
291
     * @return array|SimpleXMLElement[]
292
     */
293
    public function getBatch()
294
    {
295
        return $this->batch;
296
    }
297
298
    /**
299
     * Reset the request state
300
     * @param bool $resetResumptionToken
301
     */
302
    public function reset($resetResumptionToken = true)
303
    {
304
        $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...
305
        $this->numProcessed = 0;
306
307
        $this->currItem                 = null;
308
        $this->totalRecordsInCollection = null;
309
        $this->expireDate               = null;
310
311
        if ($resetResumptionToken) {
312
            $this->resumptionToken = null;
313
        }
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