Search by

benargo / saloon-graphql

benargo

API-agnostic GraphQL layer for Saloon

Package info

github.com/benargo/saloon-graphql

pkg:composer/benargo/saloon-graphql

Fund package maintenance!

benargo

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-10-03 13:27 UTC

This package is auto-updated.

Last update: 2026-10-03 13:32:21 UTC


README

Saloon GraphQL

Packagist PHP from Packagist Laravel versions GitHub Workflow Status (main) Total Downloads

An API-agnostic GraphQL layer for Saloon v4. It gives you a base request that sends {query, variables} as JSON, and a trait that turns GraphQL errors into exceptions, including on HTTP 200 responses.

The package depends only on saloonphp/saloon. In a Laravel app, the service provider is auto-discovered, but Laravel is not required.

Installation

composer require benargo/saloon-graphql

Usage

Writing a request

Extend GraphQLRequest, then define the endpoint, the GraphQL document and, optionally, the variables:

use Saloon\GraphQL\GraphQLRequest;

class GetCharacter extends GraphQLRequest
{
    public function __construct(private int $id) {}

    public function resolveEndpoint(): string
    {
        return '/graphql';
    }

    protected function graphQLQuery(): string
    {
        return <<<'GRAPHQL'
            query Character($id: Int!) {
                character(id: $id) { id name }
            }
            GRAPHQL;
    }

    protected function variables(): array
    {
        return ['id' => $this->id];
    }
}

Requests are sent as POST with a JSON body of exactly {"query": ..., "variables": ...}. When variables() returns an empty array, the request sends "variables": {}, because GraphQL requires an object. A list-shaped return value throws an InvalidArgumentException. Return a stdClass for an empty nested input object, since an empty array encodes as [].

The document method is called graphQLQuery() because Saloon's Request::query() already manages URL query parameters.

Handling GraphQL errors

GraphQL servers often report errors in a 200 OK response. Add HandlesGraphQLErrors to your connector, or to an individual request, so that those responses count as failures:

use Saloon\GraphQL\Traits\HandlesGraphQLErrors;
use Saloon\Http\Connector;
use Saloon\Traits\Plugins\AlwaysThrowOnErrors;

class MyApiConnector extends Connector
{
    use AlwaysThrowOnErrors;
    use HandlesGraphQLErrors;

    public function resolveBaseUrl(): string
    {
        return 'https://api.example.com';
    }
}

A response counts as a GraphQL failure when it has a JSON content type and a non-empty errors list. That includes partial results that carry both data and errors. All other responses, including malformed JSON and HTML error pages, go through Saloon's normal status-based handling, so a 500 still throws InternalServerErrorException. Responses with an error HTTP status (4xx or 5xx) keep Saloon's status-specific exception even when the body has GraphQL errors, so UnauthorizedException and TooManyRequestsException still fire. The trait's hasRequestFailed() returns null, never false, for these, so it never hides a failed HTTP status.

To accept partial results, override shouldTreatGraphQLErrorsAsFailure():

protected function shouldTreatGraphQLErrorsAsFailure(Response $response): bool
{
    return $response->json('data') === null;
}

Inspecting the exception

use Saloon\GraphQL\Exceptions\GraphQLException;

try {
    $connector->send(new GetCharacter(42));
} catch (GraphQLException $exception) {
    $exception->getErrors();                    // The raw list of GraphQL errors
    $exception->getFirstError();                // The first error message, or null
    $exception->hasErrorMatching('/not found/i');
    $exception->getResponse();                  // The Saloon response
}

GraphQLException extends Saloon's RequestException. Its default message is GraphQL request failed: {first error message}.

Throwing your own exception

Override createGraphQLException() to return a subclass:

use Saloon\GraphQL\Exceptions\GraphQLException;
use Saloon\Http\Response;

class MyApiConnector extends Connector
{
    use HandlesGraphQLErrors;

    protected function createGraphQLException(Response $response, ?Throwable $senderException): GraphQLException
    {
        return new MyApiGraphQLException($response, previous: $senderException);
    }
}

Saloon asks the request for an exception before it asks the connector. If both use HandlesGraphQLErrors, the request's createGraphQLException() wins. Put the trait in one place, or override createGraphQLException() on both.

Falling back for non-GraphQL failures

getRequestException() returns null when a response has no GraphQL errors, so Saloon's default exceptions apply. To use your own exception instead, alias the trait method:

class MyApiConnector extends Connector
{
    use HandlesGraphQLErrors {
        getRequestException as getGraphQLRequestException;
    }

    public function getRequestException(Response $response, ?Throwable $senderException): ?Throwable
    {
        return $this->getGraphQLRequestException($response, $senderException)
            ?? new MyApiException($response, previous: $senderException);
    }
}

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Thank you for considering contributing to Saloon GraphQL! Please review our contributing guide to get started.

Credits

License

Saloon GraphQL is open-sourced software licensed under the MIT license.