fedorgrecha / php-graphql-projection
GraphQL projections generator for Laravel
Installs: 2
Dependents: 0
Suggesters: 0
Security: 0
Stars: 2
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/fedorgrecha/php-graphql-projection
Requires
- php: ^8.2
- illuminate/support: ^10.0|^11.0|^12.0
- webonyx/graphql-php: ^15
Suggests
- nuwave/lighthouse: Required for generating graphql projections for Lighthouse
- rebing/graphql-laravel: Required for generating graphql projections for Rebing
README
A PHP library for generating type-safe GraphQL queries and projections for Laravel applications. Build GraphQL requests with full IDE autocomplete, type safety, and a fluent interface.
Installation
Install via Composer:
composer require --dev fedorgrecha/php-graphql-projection
Publish configuration:
php artisan vendor:publish --tag=config-graphql-projection
Configuration
Configure in config/graphql-projection.php
:
return [ 'build' => [ 'namespace' => env('GRAPHQL_PROJECTION_NAMESPACE', 'Build\\GraphQLProjection\\'), 'buildDir' => env('GRAPHQL_PROJECTION_PATH', 'build/graphql-projection'), ], 'endpoint' => env('GRAPHQL_PROJECTION_ENDPOINT', '/graphql'), 'schema' => [ 'provider' => env('GRAPHQL_PROJECTION_SCHEMA_PROVIDER', 'lighthouse'), 'providers' => [ 'lighthouse' => LighthouseSchemaProvider::class, 'rebing' => RebingSchemaProvider::class, ], ], /* * Register custom scalar types: * * Just for represent data in PHP objects */ 'typeMapping' => [ 'Email' => [ 'type' => 'string', ], 'Date' => [ 'type' => Carbon::class, 'format' => 'Y-m-d', ], ], ];
Note: Add
build/
to.gitignore
(contains generated files).
Configure Composer Autoload
Update the autoload-dev
section in your root composer.json
file:
"autoload-dev": {
"psr-4": {
"Build\\GraphQLProjection\\": "build/graphql-projection/",
// other namespaces
}
},
Note: Use namespace and path from your configuration.
After updating composer.json
, run:
composer dump-autoload
Quick Start
Generate Classes
Generate type-safe classes from your GraphQL schema:
php artisan graphql:projection
This creates:
- Query classes - Execute GraphQL operations
- Projection classes - Select fields to retrieve
- Input classes - Type-safe input data
- Type classes - Response data models
Build Your First Query
use GraphQLProjection\Client\Serialization\GraphQLQueryRequest; use GraphQLProjection\Client\Execution\GraphQLTestHelpers; class ArticleTest extends TestCase { use GraphQLTestHelpers; public function test_create_article(): void { $this->actingAs($user); // Build input data $input = ArticleInput::builder() ->title('My First Article') ->content('Article content here...') ->active(true) ->publishedAt(now()) ->build(); // Create query $query = CreateArticleGraphQLQuery::newRequest() ->input($input) ->build(); // Define projection (select fields) $projection = CreateArticleProjection::new() ->id() ->title() ->content() ->active() ->publishedAt() ->author() ->name() ->email() ->getRoot(); // Execute and get typed response $request = new GraphQLQueryRequest($query, $projection); $article = $this->graphql()->executeAndExtract($request); // Work with typed object $this->assertInstanceOf(Article::class, $article); $this->assertEquals('My First Article', $article->getTitle()); } }
Usage Examples
Nested Objects and Arrays
Work with complex nested structures effortlessly:
$input = ArticleInput::builder() ->title('Multilingual Article') ->author( AuthorInput::builder() ->name('John Doe') ->email('john@example.com') ->build() ) ->translations([ TranslationInput::builder() ->language('en') ->title('Article in English') ->content('English content...') ->build(), TranslationInput::builder() ->language('es') ->title('Artículo en Español') ->content('Contenido en español...') ->build(), ]) ->tags(['php', 'laravel', 'graphql']) ->build(); $projection = ArticleProjection::new() ->id() ->title() ->author() ->id() ->name() ->avatar() ->url() ->getParent() ->getRoot() ->translations() ->language() ->title() ->content() ->getRoot() ->tags() ->getRoot();
File Uploads
Upload single files, multiple files, or files within nested inputs:
// Single file $input = ArticleInput::builder() ->title('Article with Cover') ->coverImage(UploadedFile::fake()->image('cover.jpg')) ->build(); // Multiple files $input = ArticleInput::builder() ->title('Photo Gallery') ->images([ UploadedFile::fake()->image('photo1.jpg'), UploadedFile::fake()->image('photo2.jpg'), UploadedFile::fake()->image('photo3.jpg'), ]) ->build(); // Files in nested inputs $input = ArticleInput::builder() ->translations([ TranslationInput::builder() ->language('en') ->thumbnail(UploadedFile::fake()->image('en-thumb.jpg')) ->build(), TranslationInput::builder() ->language('es') ->thumbnail(UploadedFile::fake()->image('es-thumb.jpg')) ->build(), ]) ->build(); $query = CreateArticleGraphQLQuery::newRequest() ->input($input) ->build(); $projection = ArticleProjection::new() ->id() ->coverImage() ->url() ->size() ->mimeType(); $request = new GraphQLQueryRequest($query, $projection); $article = $this->graphql()->executeAndExtract($request);
Field Arguments
Pass arguments to specific fields:
$projection = UserProjection::new() ->id() ->name() ->posts(10, PostStatus::PUBLISHED) // Arguments: limit, status ->id() ->title() ->createdAt() ->getRoot() ->avatar('large') // Argument: size ->url() ->width() ->height() ->getRoot();
Union Types
Handle GraphQL union and interface types:
$projection = SearchProjection::new() ->results() ->onArticle() // Union type: Article ->id() ->title() ->author() ->name() ->getParent() ->getParent() ->onUser() // Union type: User ->id() ->name() ->email() ->getParent() ->onComment() // Union type: Comment ->id() ->text() ->createdAt() ->getRoot();
Pagination
Built-in support for paginated queries:
$query = ArticlesGraphQLQuery::newRequest() ->first(20) ->page(1) ->build(); $projection = ArticlesProjection::new() ->paginatorInfo() ->currentPage() ->lastPage() ->total() ->perPage() ->getRoot() ->data() ->id() ->title() ->description() ->publishedAt(); $request = new GraphQLQueryRequest($query, $projection); $paginator = $this->graphql()->executeAndExtract($request); // Work with paginated data $articles = $paginator->getData(); // Article[] $currentPage = $paginator->getPaginatorInfo()->getCurrentPage(); $total = $paginator->getPaginatorInfo()->getTotal();
Error Handling
Two approaches for handling errors:
// Option 1: executeAndExtract - throws exception on errors try { $article = $this->graphql()->executeAndExtract($request); // Work with successful response $this->assertEquals('Expected Title', $article->getTitle()); } catch (GraphQLException $e) { // Handle GraphQL errors foreach ($e->getErrors() as $error) { echo $error['message']; // Access validation errors if (isset($error['extensions']['validation'])) { $validationErrors = $error['extensions']['validation']; } } } // Option 2: execute - manual error checking $response = $this->graphql()->execute($request); if ($response->hasErrors()) { // Handle errors $errors = $response->getErrors(); $firstError = $response->getFirstError(); } else { // Get raw data or extract to typed object $rawData = $response->getData(); }
Working with Enums
Native PHP enum support:
enum ArticleStatus: string { case DRAFT = 'DRAFT'; case PUBLISHED = 'PUBLISHED'; case ARCHIVED = 'ARCHIVED'; } $input = ArticleInput::builder() ->title('My Article') ->status(ArticleStatus::PUBLISHED) ->build(); $query = CreateArticleGraphQLQuery::newRequest() ->input($input) ->build();
Date and Time Handling
Automatic Carbon conversion:
$input = ArticleInput::builder() ->publishedAt(now()) ->scheduledFor(Carbon::parse('2024-12-25 10:00:00')) ->expiresAt(now()->addDays(30)) ->build(); // Response automatically converts to Carbon $article = $this->graphql()->executeAndExtract($request); $publishedDate = $article->getPublishedAt(); // Carbon instance
API Reference
GraphQLTestHelpers (Trait)
Use in your test classes:
use GraphQLProjection\Client\Execution\GraphQLTestHelpers; class MyTest extends TestCase { use GraphQLTestHelpers; public function test_example() { $executor = $this->graphql(); // Returns GraphQLQueryExecutor } }
GraphQLQueryExecutor
Execute GraphQL requests: Methods:
execute(GraphQLQueryRequest $request): GraphQLResponse
- Execute query, return raw responseexecuteAndExtract(GraphQLQueryRequest $request): mixed
- Execute query, return typed object
GraphQLResponse
GraphQL response wrapper:
Methods:
getData(): mixed
- Get response datagetErrors(): array
- Get errors arrayhasErrors(): bool
- Check if response has errorsgetFirstError(): ?array
- Get first errorisSuccessful(): bool
- Check if request was successfulgetRawResponse(): array
- Get raw response array
GraphQLException
Exception thrown on GraphQL errors:
Methods:
getErrors(): array
- Get GraphQL errorsgetRawResponse(): array
- Get full responsegetMessage(): string
- Get error message
Projection Navigation
Navigate through nested projections:
Methods:
getRoot()
- Return to root projectiongetParent()
- Return to parent projection
$projection = RootProjection::new() ->field1() ->level1() ->field2() ->level2() ->field3() ->level3() ->field4() ->getRoot() // Back to RootProjection ->field5() ->level6() ->getParent() // Back to field5;
Builder Pattern
All Input and Type classes support builder pattern:
$input = ArticleInput::builder() ->field1($value1) ->field2($value2) ->optionalField($value3) // Optional fields ->build();
Supported GraphQL Features
Scalar Types
String
,Int
,Float
,Boolean
,ID
- Custom scalars (with automatic conversion)
Complex Types
- Objects - Nested object types
- Input Objects - Input type support
- Arrays - Lists of any type
- Nullable Fields - Optional fields with null support
- Union Types - GraphQL unions with type discrimination
- Interface Types - GraphQL interfaces
Special Features
- File Uploads - GraphQL Multipart Request Spec compliant
- Pagination - Cursor and offset-based pagination
- Enums - Native PHP enum support
- Dates - Automatic Carbon/DateTime conversion
- Custom Scalars - Extensible scalar handling
Generated Code Structure
Query Class
class CreateArticleGraphQLQuery extends GraphQLQuery { public function getOperationName(): string { return 'createArticle'; } public function getOperation(): string { return 'mutation'; // or 'query' } public function getResponseType(): string { return Article::class; } public function isList(): bool { return false; } }
Input Class
class ArticleInput extends BaseInput { private string $title; private bool $active; private ?Carbon $publishedAt; public function getTitle(): string { return $this->title; } public function setTitle(string $title): void { $this->title = $title; } public static function builder(): ArticleInputBuilder { return new ArticleInputBuilder(); } }
Type Class
class Article { private int|string $id; private string $title; private Author $author; public function getId(): int|string { return $this->id; } public function getTitle(): string { return $this->title; } public function getAuthor(): Author { return $this->author; } public static function builder(): ArticleBuilder { return new ArticleBuilder(); } }
Requirements
- PHP 8.2 or higher
- Laravel 10.x, 11.x, or 12.x
- Lighthouse or Rebing GraphQL Laravel package
Compatible GraphQL Servers
- Lighthouse - Laravel GraphQL server
- Rebing GraphQL Laravel - Laravel GraphQL implementation
License
The MIT License (MIT).