An entity, marked up with care,
Gains fields that a schema can share;
The driver resolves,
A Connection evolves,
With edges and cursors to spare.
This library provides a GraphQL driver for Doctrine ORM for use with the webonyx/graphql-php library.
It does not try to redefine how that excellent library operates. Instead, it creates types to be used
within the framework that library provides.
Many other GraphQL libraries for Doctrine ORM are available.
Some of these such as overblog/graphql-bundle
and API Platform are integrations into frameworks. But all of these libraries
use the same underlying library, webonyx/graphql-php and that library
has its own way of doing things. This library is a driver for that library and together they are framework agnostic.
Via composer:
composer require api-skeletons/doctrine-orm-graphqlFull documentation is available at https://doctrine-orm-graphql.apiskeletons.dev or in the docs directory.
- Supports all Doctrine Types, including those of DBAL 4, and allows custom types
- Pagination with the GraphQL Complete Connection Model
- Filtering and sorting of connections and sub-collections, and of to-one associations by identifier
- Batch loading of associations, so a nested query costs a fixed number of database queries
- Events for modifying queries, entity types and more
- Computed fields from entity methods
- Mutation input types from entity fields
- Multiple configuration group support
- Optional non-null types for identifiers and required columns and associations
- Entity inheritance, mapped superclasses and embeddables
- Errors a client sees only when its own request causes them
- DBAL QueryBuilder Complete Connection Model
- Attribute-based metadata
- PHP 8.4 Lazy Ghost Objects for deferred type initialization
- PSR-14 Event-Driven Architecture for query and type customization
- Custom PSR-11 Container with lazy initialization and buildable types
- Advanced hydration system with Doctrine Laminas Hydrator and extraction strategies
- Dynamic QueryBuilder generation with filter translation and event-driven query modification
- Deferred batch loading of associations to solve N+1 query problems
- Metadata as typed value objects, which may be cached
The LDOG Stack: Laravel, Doctrine ORM, and GraphQL uses this library: https://ldog.apiskeletons.dev
For a working implementation see https://graphql.lcdb.org
Add attributes to your Doctrine entities.
use ApiSkeletons\Doctrine\ORM\GraphQL\Attribute as GraphQL;
#[GraphQL\Entity]
class Artist
{
#[GraphQL\Field]
private int $id;
#[GraphQL\Field]
private string $name;
#[GraphQL\Association]
private Collection $performances;
// Each field is read with its getter: getId(), getName() and getPerformances()
}
#[GraphQL\Entity]
class Performance
{
#[GraphQL\Field]
private int $id;
#[GraphQL\Field]
private string $venue;
/**
* Not all fields need attributes.
* Only add attributes to fields you want available in GraphQL
*/
private string $city;
// getId() and getVenue()
}The Doctrine mapping attributes are left out here.
Create the driver and GraphQL schema
use ApiSkeletons\Doctrine\ORM\GraphQL\Driver;
use Doctrine\ORM\EntityManager;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
$driver = new Driver($entityManager);
$schema = new Schema([
'query' => new ObjectType([
'name' => 'query',
'fields' => [
'artists' => $driver->completeConnection(Artist::class),
],
]),
'mutation' => new ObjectType([
'name' => 'mutation',
'fields' => [
'artistUpdateName' => [
'type' => $driver->type(Artist::class),
'args' => [
'id' => Type::nonNull(Type::id()),
'input' => Type::nonNull($driver->input(Artist::class, ['name'])),
],
'resolve' => function ($root, $args) use ($driver): Artist {
$artist = $driver->get(EntityManager::class)
->getRepository(Artist::class)
->find($args['id']);
$artist->setName($args['input']['name']);
$driver->get(EntityManager::class)->flush();
return $artist;
},
],
],
]),
]);Run GraphQL queries
use GraphQL\GraphQL;
$query = '{
artists {
edges {
node {
id
name
performances {
edges {
node {
venue
}
}
}
}
}
}
}';
$result = GraphQL::executeQuery(
schema: $schema,
source: $query,
variableValues: null,
operationName: null
);
$output = $result->toArray();Run GraphQL mutations
use GraphQL\GraphQL;
$query = '
mutation ArtistUpdateName($id: ID!, $name: String!) {
artistUpdateName(id: $id, input: { name: $name }) {
id
name
}
}
';
$result = GraphQL::executeQuery(
schema: $schema,
source: $query,
variableValues: [
'id' => 1,
'name' => 'newName',
],
operationName: 'ArtistUpdateName'
);
$output = $result->toArray();For every enabled field and association, filters are available for querying.
Example
{
artists (
filter: {
name: {
contains: "Dead"
}
}
) {
edges {
node {
id
name
performances (
filter: {
venue: {
eq: "The Fillmore"
}
}
) {
edges {
node {
venue
}
}
}
}
}
}
}Each field has their own set of filters. Based on the field type, some or all of the following filters are available:
- eq - Equals.
- neq - Not equals.
- lt - Less than.
- lte - Less than or equal to.
- gt - Greater than.
- gte - Greater than or equal to.
- isnull - Is null. If value is true, the field must be null. If value is false, the field must not be null.
- between - Between. Identical to using gte & lte on the same field. Give values as
{ from: low, to: high }. - in - Exists within an array.
- notin - Does not exist within an array.
- startswith - A like query with a wildcard on the right side of the value.
- endswith - A like query with a wildcard on the left side of the value.
- contains - A like query.
- sort & sortPriority - Sort the results by a field,
ASCorDESC. Use sortPriority to sort by multiple fields.
A to-one association is filtered by the identifier of the entity it refers to, with eq, neq, in, notin and isnull.
You may exclude any filter from any entity, association, or globally.
The roots of this project go back to May 2018 with https://github.com/API-Skeletons/zf-doctrine-graphql; written for Zend Framework 2. It was migrated to the framework agnostic https://packagist.org/packages/api-skeletons/doctrine-graphql but the name of that repository was incorrect because it did not specify ORM only. So this repository was created and the others were abandoned.
This was written for graphql.etreedb.org
See LICENSE.

