From 94eaffbe8c349ce03f5b5f3e2e8d9347118ae76f Mon Sep 17 00:00:00 2001
From: Dennis de Greef
Date: Mon, 13 Mar 2017 11:52:13 +0100
Subject: [PATCH 1/2] Initial tryout of PaginatedResponse
---
src/Client.php | 31 ++----
src/PaginatedResponse.php | 184 ++++++++++++++++++++++++++++++++++++
src/Service/UserService.php | 2 +-
3 files changed, 194 insertions(+), 23 deletions(-)
create mode 100644 src/PaginatedResponse.php
diff --git a/src/Client.php b/src/Client.php
index 9db12b4..1f432ee 100644
--- a/src/Client.php
+++ b/src/Client.php
@@ -21,7 +21,7 @@
use Link0\Bunq\Middleware\ResponseSignatureMiddleware;
use Psr\Http\Message\ResponseInterface;
-final class Client
+class Client
{
/**
* @var GuzzleClient
@@ -58,7 +58,7 @@ public function __construct(Environment $environment, Keypair $keypair, PublicKe
* @param string $endpoint
* @return array
*/
- public function get(string $endpoint, array $headers = []): array
+ public function get(string $endpoint, array $headers = [])
{
return $this->processResponse(
$this->guzzle->request('GET', $endpoint, [
@@ -73,7 +73,7 @@ public function get(string $endpoint, array $headers = []): array
* @param array $headers
* @return array
*/
- public function post(string $endpoint, array $body, array $headers = []): array
+ public function post(string $endpoint, array $body, array $headers = [])
{
return $this->processResponse(
$this->guzzle->request('POST', $endpoint, [
@@ -89,7 +89,7 @@ public function post(string $endpoint, array $body, array $headers = []): array
* @param array $headers
* @return array
*/
- public function put(string $endpoint, array $body, array $headers = []): array
+ public function put(string $endpoint, array $body, array $headers = [])
{
return $this->processResponse(
$this->guzzle->request('PUT', $endpoint, [
@@ -113,30 +113,17 @@ public function delete(string $endpoint, array $headers = [])
/**
* @param ResponseInterface $response
- * @return array
+ * @return array|PaginatedResponse
*/
- private function processResponse(ResponseInterface $response): array
+ private function processResponse(ResponseInterface $response)
{
$contents = (string) $response->getBody();
- $json = json_decode($contents, true)['Response'];
-
- // Return empty responses
- if (count($json) === 0) {
- return [];
- }
+ $json = json_decode($contents, true);
- foreach ($json as $key => $value) {
- if (is_numeric($key)) {
- // Often only a single associative entry here
- foreach ($value as $type => $struct) {
- $json[$key] = $this->mapResponse($type, $struct);
- }
- }
- }
- return $json;
+ return new PaginatedResponse($this, $json);
}
- private function mapResponse(string $key, array $value)
+ public function mapResponse(string $key, array $value)
{
switch ($key) {
case 'DeviceServer':
diff --git a/src/PaginatedResponse.php b/src/PaginatedResponse.php
new file mode 100644
index 0000000..43e12b2
--- /dev/null
+++ b/src/PaginatedResponse.php
@@ -0,0 +1,184 @@
+ [
+ * 0 => [
+ * 'Foo' => [
+ * 'id' => 123,
+ * ],
+ * ],
+ * 1 => [
+ * 'Foo' => [
+ * 'id' => 456,
+ * ],
+ * ],
+ *
+ * ],
+ * 'Pagination' => [
+ * 'future_url' => '/v1/foo?newer_id=123',
+ * 'newer_url' => '/v1/foo?newer_id=456',
+ * 'older_url' => null,
+ * ]
+ * )
+ */
+final class PaginatedResponse implements IteratorAggregate, ArrayAccess
+{
+ /**
+ * @var Client
+ */
+ private $client;
+
+ /**
+ * @var array
+ */
+ private $list;
+
+ /**
+ * @var array
+ */
+ private $pagination;
+
+ /**
+ * @param Client $client
+ */
+ public function __construct(Client $client, array $body)
+ {
+ $this->client = $client;
+
+ $this->guardAndSetResponseBody($body);
+ $this->guardAndSetPagination($body);
+ }
+
+ /**
+ * @param array $body
+ * @return void
+ */
+ private function guardAndSetResponseBody(array $body)
+ {
+ if (!isset($body['Response'])) {
+ throw new InvalidArgumentException("Response body should contain key 'Response'");
+ }
+ $this->list = $body['Response'];
+ }
+
+ /**
+ * @param array $body
+ * @return void
+ */
+ private function guardAndSetPagination(array $body)
+ {
+ $this->pagination = [
+ 'future_url' => null,
+ 'newer_url' => null,
+ 'older_url' => null,
+ ];
+
+ if (isset($body['Pagination'])) {
+ $pagination = $body['Pagination'];
+
+ if (!array_key_exists('future_url', $pagination)) {
+ throw new InvalidArgumentException("Pagination should contain future_url");
+ }
+ if (!array_key_exists('newer_url', $pagination)) {
+ throw new InvalidArgumentException("Pagination should contain newer_url");
+ }
+ if (!array_key_exists('older_url', $pagination)) {
+ throw new InvalidArgumentException("Pagination should contain older_url");
+ }
+ $this->pagination = $pagination;
+ }
+ }
+
+ /**
+ * Retrieve an external iterator
+ * @link http://php.net/manual/en/iteratoraggregate.getiterator.php
+ * @return Traversable An instance of an object implementing Iterator or
+ * Traversable
+ * @since 5.0.0
+ */
+ public function getIterator()
+ {
+ foreach ($this->list as $key => $value) {
+ // mapResponse takes the struct and instantiates Value Objects
+ yield $key => $this->client->mapResponse($key, $value);
+ }
+
+ if ($this->pagination['newer_url'] !== null) {
+ foreach ($this->client->get($this->pagination['newer_url']) as $newKey => $newValue) {
+ $this->list[] = $newValue;
+ yield $newKey => $newValue;
+ }
+ }
+ }
+
+ /**
+ * Whether a offset exists
+ * @link http://php.net/manual/en/arrayaccess.offsetexists.php
+ * @param mixed $offset
+ * An offset to check for.
+ *
+ * @return boolean true on success or false on failure.
+ *
+ *
+ * The return value will be casted to boolean if non-boolean was returned.
+ * @since 5.0.0
+ */
+ public function offsetExists($offset)
+ {
+ return array_key_exists($offset, $this->list);
+ }
+
+ /**
+ * Offset to retrieve
+ * @link http://php.net/manual/en/arrayaccess.offsetget.php
+ * @param mixed $offset
+ * The offset to retrieve.
+ *
+ * @return mixed Can return all value types.
+ * @since 5.0.0
+ */
+ public function offsetGet($offset)
+ {
+
+ return $this->list[$offset];
+ }
+
+ /**
+ * Offset to set
+ * @link http://php.net/manual/en/arrayaccess.offsetset.php
+ * @param mixed $offset
+ * The offset to assign the value to.
+ *
+ * @param mixed $value
+ * The value to set.
+ *
+ * @return void
+ * @since 5.0.0
+ */
+ public function offsetSet($offset, $value)
+ {
+ throw new \LogicException("Unable to set value on immutable object");
+ }
+
+ /**
+ * Offset to unset
+ * @link http://php.net/manual/en/arrayaccess.offsetunset.php
+ * @param mixed $offset
+ * The offset to unset.
+ *
+ * @return void
+ * @since 5.0.0
+ */
+ public function offsetUnset($offset)
+ {
+ throw new \LogicException("Unable to unset value on immutable object");
+ }
+}
diff --git a/src/Service/UserService.php b/src/Service/UserService.php
index f9bf179..0505f39 100644
--- a/src/Service/UserService.php
+++ b/src/Service/UserService.php
@@ -24,7 +24,7 @@ public function __construct(Client $client)
/**
* @return User[]
*/
- public function listUsers(): array
+ public function listUsers()
{
return $this->client->get('user');
}
From 27087905b7e4185c74a1451c9ca83663d9cd4cde Mon Sep 17 00:00:00 2001
From: Dennis de Greef
Date: Mon, 13 Mar 2017 12:30:55 +0100
Subject: [PATCH 2/2] Prevent exponential cache buildup
---
src/PaginatedResponse.php | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/PaginatedResponse.php b/src/PaginatedResponse.php
index 43e12b2..2b42271 100644
--- a/src/PaginatedResponse.php
+++ b/src/PaginatedResponse.php
@@ -112,8 +112,12 @@ public function getIterator()
}
if ($this->pagination['newer_url'] !== null) {
- foreach ($this->client->get($this->pagination['newer_url']) as $newKey => $newValue) {
+ /** @var PaginatedResponse $nextPagination */
+ $nextPagination = $this->client->get($this->pagination['newer_url']);
+
+ foreach ($nextPagination as $newKey => $newValue) {
$this->list[] = $newValue;
+ unset($nextPagination->list[$newKey]);
yield $newKey => $newValue;
}
}