-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
287 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
# GraphQL Attribute Schema | ||
Build your GraphQL Schema (for [webonyx/graphql-php](https://github.com/webonyx/graphql-php)) based on attributes. | ||
|
||
## Why this library? | ||
[webonyx/graphql-php](https://github.com/webonyx/graphql-php) requires a `Schema` in order to create a GraphQL Server. | ||
This schema configuration is based on (large) PHP arrays. | ||
|
||
Wouldn't it be nice to have a library in between which can read your mutation, query and type classes instead, and create | ||
that schema configuration for you? | ||
|
||
This is where *GraphQL Attribute Schema* comes into place. By adding attributes to your classes, | ||
*GraphQL Attribute Schema* will create the schema configuration for you. | ||
|
||
## Documentation | ||
- [Getting started](docs/getting_started.md) | ||
- [Usage](docs/usage.md) | ||
|
||
## A simple example | ||
```php | ||
use Jerowork\GraphqlAttributeSchema\Attribute\Enum; | ||
use Jerowork\GraphqlAttributeSchema\Attribute\Field; | ||
use Jerowork\GraphqlAttributeSchema\Attribute\InputType; | ||
use Jerowork\GraphqlAttributeSchema\Attribute\Mutation; | ||
use Jerowork\GraphqlAttributeSchema\Attribute\Type; | ||
|
||
#[Mutation] | ||
final readonly class CreateUserMutation | ||
{ | ||
public function __invoke(CreateUserInputType $input): User | ||
{ | ||
// Do your magic; create your user here and return | ||
} | ||
} | ||
|
||
#[Query(description: 'Get a user')] | ||
final readonly class UserQuery | ||
{ | ||
public function __invoke(int $userid): User | ||
{ | ||
// Do your magic; retrieve your user and return | ||
} | ||
} | ||
|
||
#[InputType] | ||
final readonly class CreateUserInputType | ||
{ | ||
public function __construct( | ||
#[Field] | ||
public int $userId, | ||
#[Field] | ||
public string $name, | ||
#[Field(name: 'phoneNumber')] | ||
public ?string $phone, | ||
) {} | ||
} | ||
|
||
#[Type] | ||
final readonly class User | ||
{ | ||
// Define fields by property | ||
public function __construct( | ||
#[Field] | ||
public int $userId, | ||
#[Field] | ||
public string $name, | ||
public ?string $phone, | ||
#[Field(description: 'The status of the user')] | ||
public UserStatusType $status, | ||
) {} | ||
|
||
// Or define fields by method for additional logic | ||
#[Field] | ||
public function getPhoneNumber(): string | ||
{ | ||
return sprintf('+31%s', $this->phone); | ||
} | ||
} | ||
|
||
#[Enum(description: 'The status of the user')] | ||
enum UserStatusType: string | ||
{ | ||
case Created = 'CREATED'; | ||
case Removed = 'REMOVED'; | ||
} | ||
``` | ||
|
||
Will result in the following GraphQL schema: | ||
```graphql | ||
type Mutation { | ||
createUser(input: CreateUserInput!): User! | ||
} | ||
|
||
type Query { | ||
user(userId: Int!): User! | ||
} | ||
|
||
type CreateUserInput { | ||
userId: Int! | ||
name: String! | ||
phoneNumber: string | ||
} | ||
|
||
type User { | ||
userId: Int! | ||
name: String! | ||
status: UserStatus! | ||
phoneNumber: string | ||
} | ||
|
||
enum UserStatus { | ||
CREATED | ||
REMOVED | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
# Getting started | ||
|
||
## Requirements | ||
*GraphQL Attribute Schema* is a standalone library. However, it needs a couple of other libraries to work: | ||
|
||
- The main GraphQL library [webonyx/graphql-php](https://github.com/webonyx/graphql-php) | ||
- A [psr/container](https://github.com/php-fig/container) compatible container (PSR-11), e.g.: | ||
- [php-di/php-di](https://github.com/PHP-DI/PHP-DI) | ||
- [symfony/dependency-injection](https://github.com/symfony/dependency-injection), by default included in the Symfony framework | ||
|
||
## Installation | ||
Install via composer: | ||
```bash | ||
composer require jerowork/graphql-attribute-schema | ||
``` | ||
|
||
## Integration with webonyx/graphql-php | ||
In order to create a `Schema` for webonyx/graphql-php, a `SchemaBuilder` is provided in this library. | ||
This `SchemaBuilder` requires an 'Abstract Syntax Tree' (or AST), which can be created with the `Parser`. | ||
|
||
Both `SchemaBuilder` and `Parser` can be created quickly with the provided factories: | ||
|
||
```php | ||
use GraphQL\Server\StandardServer; | ||
use GraphQL\Server\ServerConfig; | ||
use Jerowork\GraphqlAttributeSchema\Parser\ParserFactory; | ||
use Jerowork\GraphqlAttributeSchema\SchemaBuilderFactory; | ||
|
||
// PSR-11 compatible container of your choice | ||
$container = new YourPsr11Container(); | ||
|
||
// 1. Create an AST based on your classes | ||
$ast = ParserFactory::create()->parse(__DIR__ . '/Path/To/GraphQL'); | ||
|
||
// 2. Create the schema configuration | ||
$schema = SchemaBuilderFactory::create($container)->build($ast); | ||
|
||
// 3. Add schema to e.g. webonyx StandardServer | ||
$server = new StandardServer(ServerConfig::create([ | ||
'schema' => $schema, | ||
])); | ||
``` | ||
|
||
*GraphQL Attribute Schema* does not create a GraphQL Server for you. | ||
How to create a GraphQL Server, please check e.g. https://webonyx.github.io/graphql-php/executing-queries/#using-server | ||
|
||
### Example GraphQL Server with Symfony | ||
|
||
As a quick-start, a simple example of a GraphQL Server with Symfony | ||
(requiring [symfony/psr-http-message-bridge](https://github.com/symfony/psr-http-message-bridge) and a PSR-7 implementation, e.g. [guzzlehttp/psr7](https://github.com/guzzle/psr7)): | ||
|
||
_Note: any error handling is absent to keep the example simple._ | ||
```php | ||
use GraphQL\Server\StandardServer; | ||
use GraphQL\Server\ServerConfig; | ||
use Jerowork\GraphqlAttributeSchema\Parser\ParserFactory; | ||
use Jerowork\GraphqlAttributeSchema\SchemaBuilderFactory; | ||
use Psr\Container\ContainerInterface; | ||
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface; | ||
use Symfony\Component\HttpFoundation\JsonResponse; | ||
use Symfony\Component\HttpFoundation\Request; | ||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\HttpKernel\Attribute\AsController; | ||
use Symfony\Component\Routing\Attribute\Route; | ||
|
||
#[AsController] | ||
final readonly class GraphQLServerController | ||
{ | ||
public function __construct( | ||
private ContainerInterface $container, | ||
private HttpMessageFactoryInterface $httpMessageFactory, | ||
) {} | ||
|
||
#[Route('/graphql', name: 'graphql.server', methods: Request::METHOD_POST)] | ||
public function __invoke(Request $request): Response | ||
{ | ||
$ast = ParserFactory::create()->parse(__DIR__ . '/Path/To/GraphQL'); | ||
$schema = SchemaBuilderFactory::create($container)->build($ast); | ||
|
||
$server = new StandardServer(ServerConfig::create([ | ||
'schema' => $schema, | ||
])); | ||
|
||
$result = $server->executePsrRequest( | ||
$this->httpMessageFactory | ||
->createRequest($request) | ||
->withParsedBody(json_decode($request->getContent(), true, flags: JSON_THROW_ON_ERROR)), | ||
); | ||
|
||
// Batch requests | ||
if (is_array($result)) { | ||
return new JsonResponse(array_map(fn($result) => $result->toArray(), $result)); | ||
} | ||
|
||
// Single request | ||
return new JsonResponse($result->toArray()); | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# Usage | ||
|
||
The following attributes can be used: | ||
- `#[Mutation]` | ||
- `#[Query]` | ||
- `#[InputType]` | ||
- `#[Type]` | ||
- `#[Enum]` | ||
- `#[Field]` | ||
- `#[Arg]` | ||
|
||
See below for more information about each attribute: | ||
|
||
## Mutation and query | ||
|
||
Mutations and queries can be defined with `#[Mutation]` and `#[Query]`. In order to configure your class as mutation or | ||
query, just add these attributes on class level: | ||
|
||
```php | ||
use Jerowork\GraphqlAttributeSchema\Attribute\Query; | ||
use Jerowork\GraphqlAttributeSchema\Attribute\Mutation; | ||
|
||
#[Mutation] | ||
final readonly YourMutation | ||
{ | ||
public function __invoke(SomeInputType $input): OutputType {} | ||
} | ||
|
||
#[Query] | ||
final readonly YourQuery | ||
{ | ||
public function __invoke(string $id, int $status) : string {} | ||
} | ||
``` | ||
|
||
### Automatic schema creation | ||
*GraphQL Attribute Schema* will read the available public method's signature: input arguments and output type. These | ||
will be automatically configured in the schema (this can be overwritten by using `#[Arg]`). | ||
|
||
Input and output can be both scalars or objects. | ||
When using objects, make sure these are defined as well with `#[InputType]` for input or `#[Type]` for output. | ||
`#[Enum]` can be used for both input and output. | ||
|
||
Also, the name of the mutation or query will be automatically read from the class name (this can be overwritten, see | ||
options). | ||
|
||
### Requirements | ||
|
||
Mutations and queries: | ||
|
||
- must be in the namespace as defined in at `Parser` creation ( | ||
see [Getting started > Integration with webonyx/graphql-php](../docs/getting_started.md#integration-with-webonyxgraphql-php)), | ||
- must be retrievable from the container (`get()`); especially for Symfony users, these should be set to public (e.g. | ||
with `#[Autoconfigure(public: true)]`), | ||
- must have only one *public* method (apart from `__construct`), which will be called on resolve. | ||
|
||
### Options | ||
|
||
Both `#[Mutation]` and `#[Query]` attribute can be configured: | ||
|
||
| Option | Description | | ||
|-------------|--------------------------------------------------------------------------| | ||
| name | Overwrite automatically read name (based on class) | | ||
| description | Set description of the mutation or query, readable in the GraphQL schema | | ||
|
||
## InputType | ||
|
||
## Type | ||
|
||
## Enum | ||
|
||
## Field | ||
|
||
## Arg |