Replies: 4 comments 2 replies
-
That's a cool idea! As you say it's not a feature in Saloon yet, but you could make custom methods that instantiate your own request collections/service classes |
Beta Was this translation helpful? Give feedback.
-
I have to say - I love this idea. How have I not thought of this before?! @Sammyjo20 Considering request collections are called as methods on the connector, would it be possible to forward any arguments that method receives, as constructor arguments to the request collection? Forwarding the arguments could at least open it up for us to manually reuse/forward those parameters in the request collection. Something like this.$movieApi = new MovieApi;
$movieActors = $movieApi->movie(42)->getActors();
// Some logic ...
$movieApi->movie(42)->updateActors(UpdateActorsData::from([
// Some payload for updating. Given plural, it should be a collection of actors, though.
]));
class MovieRequestCollection extends RequestCollection
{
public function __construct(
protected readonly int $movieId,
) {}
public function getActors(): SaloonResponse
{
return GetMovieActorsRequest::make(
$this->movieId,
)->send();
}
public function updateActors(
UpdateActorsData $payload,
): SaloonResponse {
return UpdateMovieActorsRequest::make(
$this->movieId,
$payload,
)->send();
}
}
class GetMovieActorsRequest extends SaloonRequest
{
protected string|null $method = Saloon::GET;
public function __construct(
protected readonly int $movieId,
) {}
public function defineEndpoint(): string
{
return "/movies/{$this->movieId}/actors";
}
}
class UpdateMovieActorsRequest extends SaloonRequest
{
protected string|null $method = Saloon::PATCH;
public function __construct(
protected readonly int $movieId,
protected readonly UpdateActorsData $payload,
) {}
public function defineEndpoint(): string
{
return "/movies/{$this->movieId}/actors";
}
// ... methods to define request payload and query params
} I guess another approach would be to add certain config properties to the request collection, like |
Beta Was this translation helpful? Give feedback.
-
I had the intention of trying to make (and PR) first-party support for 'single resource request collections'. Nonetheless, I replicated how I'm currently dealing with it in v2, in an internal system which has a lot of API integrations. Code samples: Example Request (GetUserRequest)/**
* @method static static make(int $id)
*/
class GetUserRequest extends Request
{
protected Method $method = Method::GET;
protected string|null $response = GetUserResponse::class;
public function __construct(
public readonly int $id,
) {}
public function resolveEndpoint(): string
{
return "/users/{$this->id}";
}
} Resourceabstract class Resource
{
public function __construct(
protected readonly MyApiConnector $myApi,
) {}
} ResourceRepositoryabstract class ResourceRepository
{
public function __construct(
protected readonly MyApiConnector $myApi,
) {}
} Userclass User extends Resource
{
public function __construct(
MyApiConnector $myApi,
protected readonly int $id,
) {
parent::__construct($myApi);
}
public function get(): PromiseInterface
{
return $this->myApi->sendAsync(GetUserRequest::make(
$this->id,
));
}
public function update(UpdateUserRequestData $data): PromiseInterface
{
return $this->myApi->sendAsync(UpdateUserRequest::make(
$this->id,
$data,
));
}
// Archive, Unarchive, ...
public function delete(): PromiseInterface
{
return $this->myApi->sendAsync(DeleteUserRequest::make(
$this->id,
));
}
} UserRepositoryclass UserRepository extends ResourceRepository
{
// total(), pages() - Both call get(),
// then just returns the total entries and total pages properties from the response.
// paginate()
public function get(): PromiseInterface
{
return $this->myApi->sendAsync(GetUsersRequest::make());
}
public function user(int $id): UserContract
{
return new User($this->myApi, $id);
}
public function create(CreateUserRequestData $data): PromiseInterface
{
return $this->myApi->sendAsync(CreateUserRequest::make(
$data,
));
}
} Example Usage$connector = new MyApiConnector;
$users = new UserRepository($connector);
$firstUser = $connector->send(new GetUserRequest(1));
$firstUser = $connector->sendAsync(new GetUserRequest(1))->wait();
$firstUser = $users->user(1)->get()->wait();
$connector->send(new ArchiveUserRequest(1));
$connector->sendAsync(new ArchiveUserRequest(1))->wait();
$users->user(1)->archive()->wait();
// Simplified example function - while each page is retrieved concurrently,
// each archive request will be sent synchronously - pooling should be used.
$archiveUser = function (GetUsersResponse $response) use ($users): void {
// Each $user is a UserData DTO (not included in examples).
foreach ($response->users() as $user) {
$users->user($user->id)->archive();
}
};
// Archive all users.
$users
->paginate()
->pool()
->withResponse($archiveUser)
->send()
->wait();
// Note here - because we *don't* pool the paginator/'request',
// they are synchronous, and we don't need to await any Promises.
foreach ($users->paginate() as $userResponse) {
//
}
$users->create(new CreateUserData([
//
]));
$totalUsers = $users->total();
$totalUserPages = $users->pages(); As seen in the example request, the request itself is still the same, regardless of how you use it. I still 'need' to pass around the Connector, which I'm not fond of (because it's mixed with the ID for the Resource, for instance). |
Beta Was this translation helpful? Give feedback.
-
We are currently working on a PoC for this very feature, head over to #176 if you want to see what we're doing. |
Beta Was this translation helpful? Give feedback.
-
Considering an API with nested enpoints like
/movies/12345/actors/12343
Instead of
Will be great to do:
Beta Was this translation helpful? Give feedback.
All reactions