Skip to content

Commit 8efb063

Browse files
authored
Merge pull request #59 from Sammyjo20/feature/nested-sdk-requests
Feature | Nested SDK Requests
2 parents af934a9 + e7a9d8f commit 8efb063

12 files changed

+379
-40
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Sammyjo20\Saloon\Exceptions;
4+
5+
class InvalidRequestKeyException extends SaloonException
6+
{
7+
//
8+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Sammyjo20\Saloon\Exceptions;
4+
5+
use Sammyjo20\Saloon\Http\SaloonConnector;
6+
7+
class NestedRequestNotFoundException extends SaloonException
8+
{
9+
public function __construct(string $method, string $collectionName, SaloonConnector $connector)
10+
{
11+
parent::__construct(sprintf('Unable to find the "%s" request method in the "%s" collection on the "%s" connector.', $method, $collectionName, get_class($connector)));
12+
}
13+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace Sammyjo20\Saloon\Helpers;
4+
5+
use ReflectionClass;
6+
use Illuminate\Support\Str;
7+
use Illuminate\Support\Collection;
8+
use Sammyjo20\Saloon\Exceptions\InvalidRequestKeyException;
9+
10+
class ProxyRequestNameHelper
11+
{
12+
/**
13+
* Recursively generate the names of requests.
14+
*
15+
* @param array $requests
16+
* @return array
17+
* @throws \ReflectionException
18+
*/
19+
public static function generateNames(array $requests): array
20+
{
21+
return (new Collection($requests))->mapWithKeys(function ($value, $key) {
22+
if (is_array($value)) {
23+
$value = static::generateNames($value);
24+
}
25+
26+
if (is_string($key)) {
27+
return [$key => $value];
28+
}
29+
30+
if (is_array($value)) {
31+
throw new InvalidRequestKeyException('Request groups must be keyed.');
32+
}
33+
34+
$guessedKey = Str::camel((new ReflectionClass($value))->getShortName());
35+
36+
return [$guessedKey => $value];
37+
})->toArray();
38+
}
39+
}

src/Helpers/RequestHelper.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Sammyjo20\Saloon\Helpers;
4+
5+
use Sammyjo20\Saloon\Http\SaloonRequest;
6+
use Sammyjo20\Saloon\Http\SaloonConnector;
7+
use Sammyjo20\Saloon\Exceptions\SaloonInvalidRequestException;
8+
9+
class RequestHelper
10+
{
11+
/**
12+
* Call a request from a connector.
13+
*
14+
* @param SaloonConnector $connector
15+
* @param string $request
16+
* @param array $arguments
17+
* @return mixed
18+
* @throws SaloonInvalidRequestException
19+
* @throws \ReflectionException
20+
*/
21+
public static function callFromConnector(SaloonConnector $connector, string $request, array $arguments = [])
22+
{
23+
$isValidRequest = ReflectionHelper::isSubclassOf($request, SaloonRequest::class);
24+
25+
if (! $isValidRequest) {
26+
throw new SaloonInvalidRequestException($request);
27+
}
28+
29+
return (new $request(...$arguments))->setConnector($connector);
30+
}
31+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace Sammyjo20\Saloon\Http;
4+
5+
use Sammyjo20\Saloon\Helpers\RequestHelper;
6+
use Sammyjo20\Saloon\Exceptions\NestedRequestNotFoundException;
7+
8+
class AnonymousRequestCollection extends RequestCollection
9+
{
10+
/**
11+
* @param SaloonConnector $connector
12+
* @param string $collectionName
13+
* @param array $requests
14+
*/
15+
public function __construct(
16+
SaloonConnector $connector,
17+
protected string $collectionName,
18+
protected array $requests,
19+
) {
20+
parent::__construct($connector);
21+
}
22+
23+
24+
/**
25+
* Call a request on a connector.
26+
*
27+
* @param string $name
28+
* @param array $arguments
29+
* @return mixed
30+
* @throws NestedRequestNotFoundException
31+
* @throws \ReflectionException
32+
* @throws \Sammyjo20\Saloon\Exceptions\SaloonInvalidRequestException
33+
*/
34+
public function __call(string $name, array $arguments)
35+
{
36+
if (! array_key_exists($name, $this->requests)) {
37+
throw new NestedRequestNotFoundException($name, $this->collectionName, $this->connector);
38+
}
39+
40+
return RequestHelper::callFromConnector($this->connector, $this->requests[$name], $arguments);
41+
}
42+
}

src/Http/RequestCollection.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Sammyjo20\Saloon\Http;
4+
5+
abstract class RequestCollection
6+
{
7+
/**
8+
* @var SaloonConnector
9+
*/
10+
protected SaloonConnector $connector;
11+
12+
/**
13+
* @param SaloonConnector $connector
14+
*/
15+
public function __construct(SaloonConnector $connector)
16+
{
17+
$this->connector = $connector;
18+
}
19+
}

src/Http/SaloonConnector.php

Lines changed: 51 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22

33
namespace Sammyjo20\Saloon\Http;
44

5-
use ReflectionClass;
6-
use Illuminate\Support\Str;
75
use Illuminate\Support\Collection;
86
use GuzzleHttp\Promise\PromiseInterface;
97
use Sammyjo20\Saloon\Clients\MockClient;
108
use Sammyjo20\Saloon\Traits\CollectsData;
9+
use Sammyjo20\Saloon\Helpers\RequestHelper;
1110
use Sammyjo20\Saloon\Traits\CollectsConfig;
1211
use Sammyjo20\Saloon\Traits\CollectsHeaders;
1312
use Sammyjo20\Saloon\Traits\CollectsHandlers;
@@ -16,6 +15,7 @@
1615
use Sammyjo20\Saloon\Traits\CollectsQueryParams;
1716
use Sammyjo20\Saloon\Traits\CollectsInterceptors;
1817
use Sammyjo20\Saloon\Traits\AuthenticatesRequests;
18+
use Sammyjo20\Saloon\Helpers\ProxyRequestNameHelper;
1919
use Sammyjo20\Saloon\Exceptions\ClassNotFoundException;
2020
use Sammyjo20\Saloon\Interfaces\SaloonConnectorInterface;
2121
use Sammyjo20\Saloon\Exceptions\SaloonInvalidRequestException;
@@ -76,23 +76,12 @@ public function boot(SaloonRequest $request): void
7676
* @param string $request
7777
* @param array $args
7878
* @return SaloonRequest
79-
* @throws ClassNotFoundException
8079
* @throws SaloonInvalidRequestException
8180
* @throws \ReflectionException
8281
*/
8382
protected function forwardCallToRequest(string $request, array $args = []): SaloonRequest
8483
{
85-
if (! class_exists($request)) {
86-
throw new ClassNotFoundException($request);
87-
}
88-
89-
$isValidRequest = ReflectionHelper::isSubclassOf($request, SaloonRequest::class);
90-
91-
if (! $isValidRequest) {
92-
throw new SaloonInvalidRequestException($request);
93-
}
94-
95-
return (new $request(...$args))->setConnector($this);
84+
return RequestHelper::callFromConnector($this, $request, $args);
9685
}
9786

9887
/**
@@ -111,19 +100,9 @@ public function getRegisteredRequests(): array
111100
return $this->registeredRequests;
112101
}
113102

114-
$requests = (new Collection($this->requests))->mapWithKeys(function ($value, $key) {
115-
if (is_string($key)) {
116-
return [$key => $value];
117-
}
103+
$this->registeredRequests = ProxyRequestNameHelper::generateNames($this->requests);
118104

119-
$guessedKey = Str::camel((new ReflectionClass($value))->getShortName());
120-
121-
return [$guessedKey => $value];
122-
})->toArray();
123-
124-
$this->registeredRequests = $requests;
125-
126-
return $requests;
105+
return $this->registeredRequests;
127106
}
128107

129108
/**
@@ -181,21 +160,15 @@ public function sendAsync(SaloonRequest $request, MockClient $mockClient = null)
181160
*
182161
* @param $method
183162
* @param $arguments
184-
* @return SaloonRequest
163+
* @return AnonymousRequestCollection|SaloonRequest
185164
* @throws ClassNotFoundException
186-
* @throws SaloonInvalidRequestException
187165
* @throws SaloonConnectorMethodNotFoundException
166+
* @throws SaloonInvalidRequestException
188167
* @throws \ReflectionException
189168
*/
190169
public function __call($method, $arguments)
191170
{
192-
if ($this->requestExists($method) === false) {
193-
throw new SaloonConnectorMethodNotFoundException($method, $this);
194-
}
195-
196-
$requests = $this->getRegisteredRequests();
197-
198-
return $this->forwardCallToRequest($requests[$method], $arguments);
171+
return $this->guessRequest($method, $arguments);
199172
}
200173

201174
/**
@@ -211,14 +184,52 @@ public function __call($method, $arguments)
211184
*/
212185
public static function __callStatic($method, $arguments)
213186
{
214-
$connector = new static;
187+
return (new static)->guessRequest($method, $arguments);
188+
}
189+
190+
/**
191+
* Attempt to guess the next request.
192+
*
193+
* @param $method
194+
* @param $arguments
195+
* @return mixed
196+
* @throws ClassNotFoundException
197+
* @throws SaloonConnectorMethodNotFoundException
198+
* @throws SaloonInvalidRequestException
199+
* @throws \ReflectionException
200+
*/
201+
protected function guessRequest($method, $arguments): mixed
202+
{
203+
if ($this->requestExists($method) === false) {
204+
throw new SaloonConnectorMethodNotFoundException($method, $this);
205+
}
206+
207+
$requests = $this->getRegisteredRequests();
208+
209+
// Work out what it is. If it is an array, pass the array into AnonymousRequestCollection($requests)
210+
// If it is a request, just forward the call to the request.
211+
212+
$resource = $requests[$method];
213+
214+
// If the request is a type of array, then it must be an anonymous request collection.
215+
216+
if (is_array($resource)) {
217+
return new AnonymousRequestCollection($this, $method, $resource);
218+
}
219+
220+
// Otherwise, check if it is a RequestCollection. If it is, then
221+
// return that class - otherwise, just forward the request.
222+
223+
if (! class_exists($resource)) {
224+
throw new ClassNotFoundException($resource);
225+
}
215226

216-
if ($connector->requestExists($method) === false) {
217-
throw new SaloonConnectorMethodNotFoundException($method, $connector);
227+
if (ReflectionHelper::isSubclassOf($resource, RequestCollection::class)) {
228+
return new $resource($this);
218229
}
219230

220-
$requests = $connector->getRegisteredRequests();
231+
// It's just a request, so forward to that.
221232

222-
return $connector->forwardCallToRequest($requests[$method], $arguments);
233+
return $this->forwardCallToRequest($resource, $arguments);
223234
}
224235
}

tests/Feature/ConnectorRequestTest.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
<?php
22

3+
use Sammyjo20\Saloon\Exceptions\InvalidRequestKeyException;
4+
use Sammyjo20\Saloon\Http\AnonymousRequestCollection;
35
use Sammyjo20\Saloon\Exceptions\ClassNotFoundException;
6+
use Sammyjo20\Saloon\Tests\Fixtures\Collections\GuessedCollection;
7+
use Sammyjo20\Saloon\Tests\Fixtures\Connectors\InvalidServiceRequestConnector;
48
use Sammyjo20\Saloon\Tests\Fixtures\Requests\UserRequest;
59
use Sammyjo20\Saloon\Tests\Fixtures\Requests\ErrorRequest;
610
use Sammyjo20\Saloon\Exceptions\SaloonInvalidRequestException;
11+
use Sammyjo20\Saloon\Tests\Fixtures\Collections\UserCollection;
712
use Sammyjo20\Saloon\Exceptions\SaloonConnectorMethodNotFoundException;
13+
use Sammyjo20\Saloon\Tests\Fixtures\Connectors\ServiceRequestConnector;
814
use Sammyjo20\Saloon\Tests\Fixtures\Connectors\RequestSelectionConnector;
915
use Sammyjo20\Saloon\Tests\Fixtures\Connectors\InvalidRequestSelectionConnector;
1016
use Sammyjo20\Saloon\Tests\Fixtures\Connectors\InvalidDefinedRequestSelectionConnector;
@@ -97,3 +103,36 @@
97103

98104
$connector->test_connector();
99105
});
106+
107+
test('a connector request can be defined in an array', function () {
108+
$connector = new ServiceRequestConnector;
109+
$request = $connector->user();
110+
111+
expect($request)->toBeInstanceOf(AnonymousRequestCollection::class);
112+
expect($request->get())->toBeInstanceOf(UserRequest::class);
113+
expect($request->get(1)->userId)->toEqual(1);
114+
});
115+
116+
test('a connector request collection can be defined', function () {
117+
$connector = new ServiceRequestConnector;
118+
$request = $connector->custom();
119+
120+
expect($request)->toBeInstanceOf(UserCollection::class);
121+
expect($request->get())->toBeInstanceOf(UserRequest::class);
122+
});
123+
124+
test('it throws an exception if you do not key an array of requests', function () {
125+
$connector = new InvalidServiceRequestConnector();
126+
127+
$this->expectException(InvalidRequestKeyException::class);
128+
$this->expectDeprecationMessage('Request groups must be keyed.');
129+
130+
$connector->custom();
131+
});
132+
133+
test('it can guess the name of a collection', function () {
134+
$connector = new ServiceRequestConnector;
135+
$collection = $connector->guessedCollection();
136+
137+
expect($collection)->toBeInstanceOf(GuessedCollection::class);
138+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace Sammyjo20\Saloon\Tests\Fixtures\Collections;
4+
5+
use Sammyjo20\Saloon\Http\RequestCollection;
6+
7+
class GuessedCollection extends RequestCollection
8+
{
9+
public function test(): bool
10+
{
11+
// Has access to $this->connector
12+
13+
return true;
14+
}
15+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Sammyjo20\Saloon\Tests\Fixtures\Collections;
4+
5+
use Sammyjo20\Saloon\Http\RequestCollection;
6+
use Sammyjo20\Saloon\Http\SaloonRequest;
7+
use Sammyjo20\Saloon\Tests\Fixtures\Requests\UserRequest;
8+
9+
class UserCollection extends RequestCollection
10+
{
11+
/**
12+
* @return SaloonRequest
13+
*/
14+
public function get(): SaloonRequest
15+
{
16+
return $this->connector->request(new UserRequest);
17+
}
18+
}

0 commit comments

Comments
 (0)