Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ jobs:
dependency-versions: ${{ matrix.dependency-versions }}

- name: Run PHPUnit
run: vendor/bin/phpunit
run: vendor/bin/phpunit
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
### Unreleased

### v2.5.0 (2025-08-05)

* Add VenueSearchIterator to make querying venues endpoint easier

### v2.4.0 (2025-05-27)

* Support PHP 8.4
Expand Down
5 changes: 5 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
"FestivalsApi\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"test\\unit\\FestivalsApi\\": "test/unit"
}
},
"config": {
"preferred-install": "dist"
}
Expand Down
89 changes: 89 additions & 0 deletions src/AbstractSearchIterator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

namespace FestivalsApi;

use GuzzleHttp\Exception\GuzzleException;
use IteratorAggregate;
use LogicException;
use Traversable;
use function get_class;
use function is_array;

abstract class AbstractSearchIterator implements IteratorAggregate
{
protected int $last_result_count;

protected ?int $page_size = NULL;

protected ?array $query = NULL;

protected int $request_count = 0;

public function __construct(
protected FestivalsApiClient $client
) {
}

/**
* @throws FestivalsApiClientException if API client encounters an error
* @throws GuzzleException if Guzzle encounters an error
* @throws LogicException if no query set
*/
public function getIterator(): Traversable
{
$this->throwIfNoQuerySet();

$this->query['size'] = $this->page_size;
$this->query['from'] = 0;
do {
$results = $this->makeApiCall();
++$this->request_count;

$this->last_result_count = count($results);

foreach ($results as $result) {
yield $result;
}
$this->query['from'] += $this->page_size;
} while ($this->last_result_count === $this->page_size);
}

/**
* Total number of calls to API made by this query.
*/
public function getNoOfRequestsMadeByQuery(): int
{
return $this->request_count;
}

/**
* Sets the search query.
*
* @param array $query the query parameters eg ['festival'=>'jazz', title='Blue']
* @param int $page_size the number of events to return per request, defaults to API max limit
*/
public function setQuery(array $query, int $page_size = 100): void
{
$this->query = $query;
$this->page_size = $page_size;
$this->request_count = 0;
}

/**
* Execute the query and return the events.
*
* @throws GuzzleException
* @throws FestivalsApiClientException
*/
protected abstract function makeApiCall(): array;

/**
* @throws LogicException if no query set
*/
protected function throwIfNoQuerySet(): void
{
if ( ! is_array($this->query)) {
throw new LogicException('You must call '.get_class($this).'::setQuery() before iterating result');
}
}
}
78 changes: 9 additions & 69 deletions src/EventSearchIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,67 +10,17 @@

use FestivalsApi\Result\EventSearchResult;
use GuzzleHttp\Exception\GuzzleException;
use IteratorAggregate;
use LogicException;
use Traversable;
use function get_class;
use function is_array;

class EventSearchIterator implements IteratorAggregate
/**
* @final since 2.5.0
*/
class EventSearchIterator extends AbstractSearchIterator
{

protected EventSearchResult $last_result;

protected ?int $page_size = NULL;

protected ?array $query = NULL;

protected int $request_count = 0;

public function __construct(protected FestivalsApiClient $client)
{
}

/**
* @throws FestivalsApiClientException if API client encounters an error
* @throws GuzzleException if Guzzle encounters an error
* @throws LogicException if no query set
*/
public function getIterator(): Traversable
{
$this->throwIfNoQuerySet();

$this->query['size'] = $this->page_size;
$this->query['from'] = 0;
do {
$events = $this->makeApiCall();
foreach ($events as $event) {
yield $event;
}
$this->query['from'] += $this->page_size;
} while (count($this->last_result->getEvents()) === $this->page_size);
}

/**
* Total number of calls to API made by this query
*/
public function getNoOfRequestsMadeByQuery(): int
{
return $this->request_count;
}

/**
* Sets the search query
*
* @param array $query the query parameters eg ['festival'=>'jazz', title='Blue']
* @param int $page_size the number of events to return per request, defaults to API max limit
* @deprecated
*/
public function setQuery(array $query, int $page_size = 100): void
{
$this->query = $query;
$this->page_size = $page_size;
$this->request_count = 0;
}
protected EventSearchResult $last_result;

/**
* Execute the query and return the events
Expand All @@ -80,20 +30,10 @@ public function setQuery(array $query, int $page_size = 100): void
*/
protected function makeApiCall(): array
{
$this->request_count++;
$this->last_result = $this->client->searchEvents($this->query);

return $this->last_result->getEvents();
}
$result = $this->client->searchEvents($this->query);
$this->last_result = $result;

/**
* @throws LogicException if no query set
*/
protected function throwIfNoQuerySet(): void
{
if ( ! is_array($this->query)) {
throw new LogicException("You must call ".get_class($this)."::setQuery() before iterating result");
}
return $result->getEvents();
}

}
2 changes: 1 addition & 1 deletion src/FestivalsApiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public function searchVenues(array $query): VenueSearchResult
$response = $this->sendRequest($request);

return new VenueSearchResult(
venues: $this->decodeJsonResponse($response),
results: $this->decodeJsonResponse($response),
url: $request->getUri(),
total_results: (int) $response->getHeaderLine('x-total-results') ?: 0
);
Expand Down
9 changes: 9 additions & 0 deletions src/MockFestivalsApiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Exception;
use FestivalsApi\Result\EventSearchResult;
use FestivalsApi\Result\SingleEventResult;
use FestivalsApi\Result\VenueSearchResult;
use PHPUnit\Framework\Assert;
use function array_pop;
use function array_reverse;
Expand Down Expand Up @@ -67,4 +68,12 @@ public function searchEvents(array $query): EventSearchResult
return new EventSearchResult($response, 'WORK IT OUT YOURSELF', $this->total_results);
}

public function searchVenues(array $query): VenueSearchResult
{
$this->called_with[] = $query;
$response = array_pop($this->responses) ?: [];

return new VenueSearchResult($response, 'WORK IT OUT YOURSELF', $this->total_results);
}

}
29 changes: 29 additions & 0 deletions src/Result/AbstractSearchResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
/**
* @author Festivals Edinburgh <support@api.edinburghfestivalcity.com>
* @licence BSD-3-Clause
*/


namespace FestivalsApi\Result;


abstract class AbstractSearchResult
{
public function __construct(
protected array $results,
protected string $url,
protected int $total_results
) {
}

public function getTotalResults(): int
{
return $this->total_results;
}

public function getUrl(): string
{
return $this->url;
}
}
49 changes: 2 additions & 47 deletions src/Result/EventSearchResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,56 +8,11 @@
namespace FestivalsApi\Result;


class EventSearchResult
class EventSearchResult extends AbstractSearchResult
{
/**
* @var array
*/
protected $events;

/**
* @var int
*/
protected $total_results;

/**
* @var string
*/
protected $url;

/**
* @param array $events
* @param string $url
* @param int $total_results
*/
public function __construct(array $events, $url, $total_results)
{
$this->events = $events;
$this->url = $url;
$this->total_results = $total_results;
}

/**
* @return array
*/
public function getEvents(): array
{
return $this->events;
return $this->results;
}

/**
* @return int
*/
public function getTotalResults(): int
{
return $this->total_results;
}

/**
* @return string
*/
public function getUrl(): string
{
return $this->url;
}
}
21 changes: 2 additions & 19 deletions src/Result/VenueSearchResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,10 @@
namespace FestivalsApi\Result;


class VenueSearchResult
class VenueSearchResult extends AbstractSearchResult
{
public function __construct(
protected array $venues,
protected string $url,
protected int $total_results
) {
}

public function getVenues(): array
{
return $this->venues;
}

public function getTotalResults(): int
{
return $this->total_results;
}

public function getUrl(): string
{
return $this->url;
return $this->results;
}
}
24 changes: 24 additions & 0 deletions src/VenueSearchIterator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php
/**
* @author Festivals Edinburgh <support@api.edinburghfestivalcity.com>
* @licence BSD-3-Clause
*/

namespace FestivalsApi;

use GuzzleHttp\Exception\GuzzleException;

final class VenueSearchIterator extends AbstractSearchIterator
{
/**
* Execute the query and return the venues
*
* @throws GuzzleException
* @throws FestivalsApiClientException
*/
protected function makeApiCall(): array
{
return $this->client->searchVenues($this->query)->getVenues();
}

}
Loading