simpod / graphql-utils
GraphQL Utils for graphql-php
Fund package maintenance!
simPod
Installs: 80 072
Dependents: 1
Suggesters: 0
Security: 0
Stars: 12
Watchers: 5
Forks: 3
Open Issues: 1
Requires
- php: ^8.2
- webonyx/graphql-php: ^15.4
Requires (Dev)
- doctrine/coding-standard: ^12.0
- infection/infection: ^0.27.0
- phpstan/extension-installer: ^1.1
- phpstan/phpstan: ^1.3
- phpstan/phpstan-phpunit: ^1.0
- phpstan/phpstan-strict-rules: ^1.1
- phpunit/phpunit: ^10.1
- psalm/plugin-phpunit: ^0.19.0
- roave/infection-static-analysis-plugin: ^1.31
- vimeo/psalm: ^5.0
README
Installation
Add as Composer dependency:
composer require simpod/graphql-utils
Features
Schema Builders
Instead of defining your schema as an array, use can use more objective-oriented approach. This library provides set of strictly typed builders that help you build your schema:
- EnumBuilder
- FieldBuilder
- InputFieldBuilder
- InputObjectBuilder
- InterfaceBuilder
- ObjectBuilder
- TypeBuilder
- UnionBuilder
ObjectBuilder and FieldBuilder
✔️ Standard way with webonyx/graphql-php
<?php use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ResolveInfo; $userType = new ObjectType([ 'name' => 'User', 'description' => 'Our blog visitor', 'fields' => [ 'firstName' => [ 'type' => Type::string(), 'description' => 'User first name' ], 'email' => Type::string() ], 'resolveField' => static function(User $user, $args, $context, ResolveInfo $info) { switch ($info->fieldName) { case 'name': return $user->getName(); case 'email': return $user->getEmail(); default: return null; } } ]);
✨ The same can be produced in objective way
<?php use GraphQL\Type\Definition\ObjectType; use GraphQL\Type\Definition\ResolveInfo; use SimPod\GraphQLUtils\Builder\ObjectBuilder; $userType = new ObjectType( ObjectBuilder::create('User') ->setDescription('Our blog visitor') ->setFields([ FieldBuilder::create('firstName', Type::string()) ->setDescription('User first name') ->build(), FieldBuilder::create('email', Type::string())->build(), ]) ->setFieldResolver( static function(User $user, $args, $context, ResolveInfo $info) { switch ($info->fieldName) { case 'name': return $user->getName(); case 'email': return $user->getEmail(); default: return null; } } ) ->build() );
EnumBuilder
✔️ Standard way with webonyx/graphql-php
<?php use GraphQL\Type\Definition\EnumType; $episodeEnum = new EnumType([ 'name' => 'Episode', 'description' => 'One of the films in the Star Wars Trilogy', 'values' => [ 'NEWHOPE' => [ 'value' => 4, 'description' => 'Released in 1977.' ], 'EMPIRE' => [ 'value' => 5, 'description' => 'Released in 1980.' ], 'JEDI' => [ 'value' => 6, 'description' => 'Released in 1983.' ], ] ]);
✨ The same can be produced in objective way
<?php use GraphQL\Type\Definition\EnumType; use SimPod\GraphQLUtils\Builder\EnumBuilder; $episodeEnum = new EnumType( EnumBuilder::create('Episode') ->setDescription('One of the films in the Star Wars Trilogy') ->addValue(4, 'NEWHOPE', 'Released in 1977.') ->addValue(5, 'EMPIRE', 'Released in 1980.') ->addValue(6, 'JEDI', 'Released in 1983.') ->build() );
InterfaceBuilder
✔️ Standard way with webonyx/graphql-php
<?php use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\Type; $character = new InterfaceType([ 'name' => 'Character', 'description' => 'A character in the Star Wars Trilogy', 'fields' => [ 'id' => [ 'type' => Type::nonNull(Type::string()), 'description' => 'The id of the character.', ], 'name' => [ 'type' => Type::string(), 'description' => 'The name of the character.' ] ], 'resolveType' => static function ($value) : object { if ($value->type === 'human') { return MyTypes::human(); } return MyTypes::droid(); } ]);
✨ The same can be produced in objective way
<?php use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\Type; use SimPod\GraphQLUtils\Builder\InterfaceBuilder; use SimPod\GraphQLUtils\Builder\FieldBuilder; $character = new InterfaceType( InterfaceBuilder::create('Character') ->setDescription('A character in the Star Wars Trilogy') ->setFields([ FieldBuilder::create('id', Type::nonNull(Type::string())) ->setDescription('The id of the character.') ->build(), FieldBuilder::create('name', Type::string()) ->setDescription('The name of the character.') ->build() ]) ->setResolveType( static function ($value) : object { if ($value->type === 'human') { return MyTypes::human(); } return MyTypes::droid(); } ) ->build() );
UnionBuilder
✔️ Standard way with webonyx/graphql-php
<?php use GraphQL\Type\Definition\UnionType; $searchResultType = new UnionType([ 'name' => 'SearchResult', 'types' => [ MyTypes::story(), MyTypes::user() ], 'resolveType' => static function($value) { if ($value->type === 'story') { return MyTypes::story(); } return MyTypes::user(); } ]);
✨ The same can be produced in objective way
<?php use SimPod\GraphQLUtils\Builder\UnionBuilder; $character = new UnionType( UnionBuilder::create( 'SearchResult', [ MyTypes::story(), MyTypes::user() ] ) ->setResolveType( static function($value) { if ($value->type === 'story') { return MyTypes::story(); } return MyTypes::user(); } ) ->build() );
Error Handling
Extending your exception with SimPod\GraphQLUtils\Error\Error
forces you to implement getType()
method.
Example Error class
<?php use SimPod\GraphQLUtils\Error\Error; final class InvalidCustomerIdProvided extends Error { private const TYPE = 'INVALID_CUSTOMER_ID_PROVIDED'; public static function noneGiven() : self { return new self('No CustomerId provided'); } public function getType() : string { return self::TYPE; } public function isClientSafe() : bool { return true; } }
Create your formatter
<?php use GraphQL\Error\Error; use SimPod\GraphQLUtils\Error\FormattedError; $formatError = static function (Error $error) : array { if (! $error->isClientSafe()) { // eg. log error } return FormattedError::createFromException($error); }; $errorFormatterCallback = static function (Error $error) use ($formatError) : array { return $formatError($error); }; $config = GraphQL::executeQuery(/* $args */) ->setErrorFormatter($errorFormatterCallback) ->setErrorsHandler( static function (array $errors, callable $formatter) : array { return array_map($formatter, $errors); } );
Error types will then be provided in your response so client can easier identify the error type
{ "errors": [ { "message": "No CustomerId provided", "extensions": { "type": "INVALID_CUSTOMER_ID_PROVIDED", "category": "validation" } } ] }