Installation Add the bundle via Composer:
composer require youshido/graphql-bundle
Enable it in config/bundles.php:
return [
// ...
Youshido\GraphQLBundle\YoushidoGraphQLBundle::class => ['all' => true],
];
Basic Configuration
Define a schema in config/packages/youshido_graphql.yaml:
youshido_graphql:
schema:
query: App\\GraphQL\\Query
mutation: App\\GraphQL\\Mutation
subscription: App\\GraphQL\\Subscription
debug: '%kernel.debug%'
First Query
Create a simple Query class (e.g., src/GraphQL/Query.php):
namespace App\GraphQL;
use Youshido\GraphQL\Config\FieldConfig;
use Youshido\GraphQL\Execution\ResolveInfo;
use Youshido\GraphQL\Execution\Types\ResolveType;
use Youshido\GraphQL\Execution\Types\ScalarType;
use Youshido\GraphQL\Type\Definition\ObjectType;
class Query
{
public function hello(FieldConfig $config, ResolveInfo $info)
{
return 'World';
}
}
Test via HTTP:
query {
hello
}
Routing
Add a route in config/routes.yaml:
graphql:
path: /graphql
controller: youshido_graphql.controller.graphql
methods: [GET, POST]
Type Hierarchy
Define types in dedicated classes (e.g., src/GraphQL/Type/UserType.php):
namespace App\GraphQL\Type;
use Youshido\GraphQL\Type\Definition\ObjectType;
class UserType extends ObjectType
{
public function build($config)
{
$config->addField([
'type' => 'String',
'name' => 'name',
'resolve' => function ($root) { return $root->name; },
]);
}
}
Resolvers Use dependency injection for services:
use App\Service\UserService;
class Query
{
private $userService;
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function user(FieldConfig $config, ResolveInfo $info)
{
return $this->userService->find($config->args['id']);
}
}
Query/Mutation Separation
Split logic into Query and Mutation classes for clarity.
Example Mutation:
class Mutation
{
public function createUser(FieldConfig $config, ResolveInfo $info)
{
$user = new User();
$user->name = $config->args['name'];
// Save logic...
return $user;
}
}
Input Types Define input types for mutations:
namespace App\GraphQL\Type;
use Youshido\GraphQL\Type\Definition\InputObjectType;
class CreateUserInputType extends InputObjectType
{
public function build($config)
{
$config->addField([
'type' => 'String',
'name' => 'name',
]);
}
}
Pagination
Use Connection types for paginated queries:
use Youshido\GraphQL\Type\Definition\ObjectType;
use Youshido\GraphQL\Type\Relay\ConnectionType;
class UserConnection extends ConnectionType
{
public function build($config)
{
$config->setType('UserType');
$config->setResolveFn(function ($root, $args) {
return User::paginate($args['first'], $args['after']);
});
}
}
Symfony Forms Bind GraphQL inputs to Symfony forms for validation:
use Symfony\Component\Form\FormFactoryInterface;
class Mutation
{
private $formFactory;
public function __construct(FormFactoryInterface $formFactory)
{
$this->formFactory = $formFactory;
}
public function submitForm(FieldConfig $config, ResolveInfo $info)
{
$form = $this->formFactory->createBuilder()
->add('email', EmailType::class)
->getForm();
$form->submit($config->args['input']);
if ($form->isValid()) {
// Process data...
}
}
}
Doctrine ORM Use Doctrine repositories in resolvers:
use Doctrine\ORM\EntityManagerInterface;
class Query
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function users(FieldConfig $config, ResolveInfo $info)
{
return $this->em->getRepository(User::class)->findAll();
}
}
Circular Dependencies
Avoid circular references in type definitions (e.g., User referencing Post which references User). Use lazy loading or interfaces.
Scalar Type Mismatches
Ensure resolver return types match declared GraphQL types. For example, returning null when a non-nullable type is expected will throw an error.
Debugging Resolvers
Use var_dump($config->args) or var_dump($root) to inspect input data during development. Enable debug mode in config:
youshido_graphql:
debug: true
Deprecated API The package is last updated in 2019. Some Symfony 5+ features (e.g., attribute routing) may require workarounds or custom middleware.
GraphQL Playground
Access /graphql with debug mode enabled to use the built-in playground for interactive testing.
Error Handling Wrap resolvers in try-catch blocks to return user-friendly errors:
public function riskyOperation(FieldConfig $config, ResolveInfo $info)
{
try {
// ...
} catch (\Exception $e) {
throw new GraphQL\Error\GraphQLException($e->getMessage());
}
}
Logging Enable Monolog for GraphQL events:
youshido_graphql:
listeners:
- App\GraphQL\Listener\LoggingListener
Schema Auto-Discovery
The bundle does not auto-discover types. Manually register types in build() methods or use a service to load them dynamically.
Custom Directives Register directives in the schema config:
youshido_graphql:
schema:
directives:
- App\GraphQL\Directive\DeprecatedDirective
Middleware Add middleware for authentication/authorization:
use Youshido\GraphQL\Execution\Middleware\Middleware;
class AuthMiddleware implements Middleware
{
public function execute($root, FieldConfig $config, ResolveInfo $info, callable $next)
{
if (!$this->isAuthenticated()) {
throw new GraphQL\Error\GraphQLException('Unauthorized');
}
return $next($root, $config, $info);
}
}
Register in config:
youshido_graphql:
middleware:
- App\GraphQL\Middleware\AuthMiddleware
Custom Scalar Types
Extend ScalarType for custom scalars (e.g., DateTime):
namespace App\GraphQL\Type;
use Youshido\GraphQL\Execution\Types\ScalarType;
class DateTimeType extends ScalarType
{
public function __construct()
{
parent::__construct('DateTime');
}
public function serialize($value)
{
return $value->format(\DateTime::ATOM);
}
public function parseValue($value)
{
return new \DateTime($value);
}
}
Subscription Support
Use ReactPHP for subscriptions (requires additional setup):
youshido_graphql:
schema:
subscription: App\GraphQL\Subscription
subscriptions:
transport: react
How can I help you explore Laravel packages today?