Skip to content

Commit ddbc747

Browse files
committed
Add VenueSearchIterator
To make it easier querying paginated venues endpoint
1 parent 58f0a39 commit ddbc747

14 files changed

Lines changed: 366 additions & 299 deletions

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,4 @@ jobs:
4141
dependency-versions: ${{ matrix.dependency-versions }}
4242

4343
- name: Run PHPUnit
44-
run: vendor/bin/phpunit
44+
run: vendor/bin/phpunit

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
### Unreleased
22

3+
### v2.4.1 (2025-08-05)
4+
5+
* Add VenueSearchIterator to make querying venues endpoint easier
6+
37
### v2.4.0 (2025-05-27)
48

59
* Support PHP 8.4

composer.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@
2828
"FestivalsApi\\": "src/"
2929
}
3030
},
31+
"autoload-dev": {
32+
"psr-4": {
33+
"test\\unit\\FestivalsApi\\": "test/unit"
34+
}
35+
},
3136
"config": {
3237
"preferred-install": "dist"
3338
}

src/AbstractSearchIterator.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
namespace FestivalsApi;
4+
5+
use GuzzleHttp\Exception\GuzzleException;
6+
use IteratorAggregate;
7+
use LogicException;
8+
use Traversable;
9+
use function get_class;
10+
use function is_array;
11+
12+
abstract class AbstractSearchIterator implements IteratorAggregate
13+
{
14+
protected int $last_result_count;
15+
16+
protected ?int $page_size = NULL;
17+
18+
protected ?array $query = NULL;
19+
20+
protected int $request_count = 0;
21+
22+
public function __construct(
23+
protected FestivalsApiClient $client
24+
) {
25+
}
26+
27+
/**
28+
* @throws FestivalsApiClientException if API client encounters an error
29+
* @throws GuzzleException if Guzzle encounters an error
30+
* @throws LogicException if no query set
31+
*/
32+
public function getIterator(): Traversable
33+
{
34+
$this->throwIfNoQuerySet();
35+
36+
$this->query['size'] = $this->page_size;
37+
$this->query['from'] = 0;
38+
do {
39+
$results = $this->makeApiCall();
40+
++$this->request_count;
41+
42+
$this->last_result_count = count($results);
43+
44+
foreach ($results as $result) {
45+
yield $result;
46+
}
47+
$this->query['from'] += $this->page_size;
48+
} while ($this->last_result_count === $this->page_size);
49+
}
50+
51+
/**
52+
* Total number of calls to API made by this query.
53+
*/
54+
public function getNoOfRequestsMadeByQuery(): int
55+
{
56+
return $this->request_count;
57+
}
58+
59+
/**
60+
* Sets the search query.
61+
*
62+
* @param array $query the query parameters eg ['festival'=>'jazz', title='Blue']
63+
* @param int $page_size the number of events to return per request, defaults to API max limit
64+
*/
65+
public function setQuery(array $query, int $page_size = 100): void
66+
{
67+
$this->query = $query;
68+
$this->page_size = $page_size;
69+
$this->request_count = 0;
70+
}
71+
72+
/**
73+
* Execute the query and return the events.
74+
*
75+
* @throws GuzzleException
76+
* @throws FestivalsApiClientException
77+
*/
78+
protected abstract function makeApiCall(): array;
79+
80+
/**
81+
* @throws LogicException if no query set
82+
*/
83+
protected function throwIfNoQuerySet(): void
84+
{
85+
if ( ! is_array($this->query)) {
86+
throw new LogicException('You must call '.get_class($this).'::setQuery() before iterating result');
87+
}
88+
}
89+
}

src/EventSearchIterator.php

Lines changed: 2 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -8,70 +8,11 @@
88
namespace FestivalsApi;
99

1010

11-
use FestivalsApi\Result\EventSearchResult;
1211
use GuzzleHttp\Exception\GuzzleException;
13-
use IteratorAggregate;
14-
use LogicException;
15-
use Traversable;
16-
use function get_class;
17-
use function is_array;
1812

19-
class EventSearchIterator implements IteratorAggregate
13+
class EventSearchIterator extends AbstractSearchIterator
2014
{
2115

22-
protected EventSearchResult $last_result;
23-
24-
protected ?int $page_size = NULL;
25-
26-
protected ?array $query = NULL;
27-
28-
protected int $request_count = 0;
29-
30-
public function __construct(protected FestivalsApiClient $client)
31-
{
32-
}
33-
34-
/**
35-
* @throws FestivalsApiClientException if API client encounters an error
36-
* @throws GuzzleException if Guzzle encounters an error
37-
* @throws LogicException if no query set
38-
*/
39-
public function getIterator(): Traversable
40-
{
41-
$this->throwIfNoQuerySet();
42-
43-
$this->query['size'] = $this->page_size;
44-
$this->query['from'] = 0;
45-
do {
46-
$events = $this->makeApiCall();
47-
foreach ($events as $event) {
48-
yield $event;
49-
}
50-
$this->query['from'] += $this->page_size;
51-
} while (count($this->last_result->getEvents()) === $this->page_size);
52-
}
53-
54-
/**
55-
* Total number of calls to API made by this query
56-
*/
57-
public function getNoOfRequestsMadeByQuery(): int
58-
{
59-
return $this->request_count;
60-
}
61-
62-
/**
63-
* Sets the search query
64-
*
65-
* @param array $query the query parameters eg ['festival'=>'jazz', title='Blue']
66-
* @param int $page_size the number of events to return per request, defaults to API max limit
67-
*/
68-
public function setQuery(array $query, int $page_size = 100): void
69-
{
70-
$this->query = $query;
71-
$this->page_size = $page_size;
72-
$this->request_count = 0;
73-
}
74-
7516
/**
7617
* Execute the query and return the events
7718
*
@@ -80,20 +21,7 @@ public function setQuery(array $query, int $page_size = 100): void
8021
*/
8122
protected function makeApiCall(): array
8223
{
83-
$this->request_count++;
84-
$this->last_result = $this->client->searchEvents($this->query);
85-
86-
return $this->last_result->getEvents();
87-
}
88-
89-
/**
90-
* @throws LogicException if no query set
91-
*/
92-
protected function throwIfNoQuerySet(): void
93-
{
94-
if ( ! is_array($this->query)) {
95-
throw new LogicException("You must call ".get_class($this)."::setQuery() before iterating result");
96-
}
24+
return $this->client->searchEvents($this->query)->getEvents();
9725
}
9826

9927
}

src/FestivalsApiClient.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public function searchVenues(array $query): VenueSearchResult
9696
$response = $this->sendRequest($request);
9797

9898
return new VenueSearchResult(
99-
venues: $this->decodeJsonResponse($response),
99+
results: $this->decodeJsonResponse($response),
100100
url: $request->getUri(),
101101
total_results: (int) $response->getHeaderLine('x-total-results') ?: 0
102102
);

src/MockFestivalsApiClient.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Exception;
1010
use FestivalsApi\Result\EventSearchResult;
1111
use FestivalsApi\Result\SingleEventResult;
12+
use FestivalsApi\Result\VenueSearchResult;
1213
use PHPUnit\Framework\Assert;
1314
use function array_pop;
1415
use function array_reverse;
@@ -67,4 +68,12 @@ public function searchEvents(array $query): EventSearchResult
6768
return new EventSearchResult($response, 'WORK IT OUT YOURSELF', $this->total_results);
6869
}
6970

71+
public function searchVenues(array $query): VenueSearchResult
72+
{
73+
$this->called_with[] = $query;
74+
$response = array_pop($this->responses) ?: [];
75+
76+
return new VenueSearchResult($response, 'WORK IT OUT YOURSELF', $this->total_results);
77+
}
78+
7079
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
/**
3+
* @author Festivals Edinburgh <support@api.edinburghfestivalcity.com>
4+
* @licence BSD-3-Clause
5+
*/
6+
7+
8+
namespace FestivalsApi\Result;
9+
10+
11+
abstract class AbstractSearchResult
12+
{
13+
public function __construct(
14+
protected array $results,
15+
protected string $url,
16+
protected int $total_results
17+
) {
18+
}
19+
20+
public function getTotalResults(): int
21+
{
22+
return $this->total_results;
23+
}
24+
25+
public function getUrl(): string
26+
{
27+
return $this->url;
28+
}
29+
}

src/Result/EventSearchResult.php

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,56 +8,11 @@
88
namespace FestivalsApi\Result;
99

1010

11-
class EventSearchResult
11+
class EventSearchResult extends AbstractSearchResult
1212
{
13-
/**
14-
* @var array
15-
*/
16-
protected $events;
17-
18-
/**
19-
* @var int
20-
*/
21-
protected $total_results;
22-
23-
/**
24-
* @var string
25-
*/
26-
protected $url;
27-
28-
/**
29-
* @param array $events
30-
* @param string $url
31-
* @param int $total_results
32-
*/
33-
public function __construct(array $events, $url, $total_results)
34-
{
35-
$this->events = $events;
36-
$this->url = $url;
37-
$this->total_results = $total_results;
38-
}
39-
40-
/**
41-
* @return array
42-
*/
4313
public function getEvents(): array
4414
{
45-
return $this->events;
15+
return $this->results;
4616
}
4717

48-
/**
49-
* @return int
50-
*/
51-
public function getTotalResults(): int
52-
{
53-
return $this->total_results;
54-
}
55-
56-
/**
57-
* @return string
58-
*/
59-
public function getUrl(): string
60-
{
61-
return $this->url;
62-
}
6318
}

src/Result/VenueSearchResult.php

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,10 @@
88
namespace FestivalsApi\Result;
99

1010

11-
class VenueSearchResult
11+
class VenueSearchResult extends AbstractSearchResult
1212
{
13-
public function __construct(
14-
protected array $venues,
15-
protected string $url,
16-
protected int $total_results
17-
) {
18-
}
19-
2013
public function getVenues(): array
2114
{
22-
return $this->venues;
23-
}
24-
25-
public function getTotalResults(): int
26-
{
27-
return $this->total_results;
28-
}
29-
30-
public function getUrl(): string
31-
{
32-
return $this->url;
15+
return $this->results;
3316
}
3417
}

0 commit comments

Comments
 (0)