There are two versions, 11 and 12, of this library which support different event manager versions. See Versions and Event Manager Support for more information.
Each connection
may listen for a QueryBuilder
event.
The event has a getQueryBuilder()
method to retrieve the Doctrine QueryBuilder object before it is executed.
The Doctrine QueryBuilder object may be modified to filter the data for the logged in user and such.
This can be used as a security layer and can be used to make
customizations to QueryBuilder
objects. QueryBuilders are built then
triggered through an event. Listen to this event and modify the passed
QueryBuilder to apply your security.
Event names are passed as a second parameter to a $driver->resolve()
.
In the code below, the event Artist::class . '.queryBuilder'
will fire:
use ApiSkeletons\Doctrine\ORM\GraphQL\Driver;
use App\ORM\Entity\Artist;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Schema;
$schema = new Schema([
'query' => new ObjectType([
'name' => 'query',
'fields' => [
'artists' => [
'type' => $driver->connection(Artist::class),
'args' => [
'filter' => $driver->filter(Artist::class),
],
'resolve' => $driver->resolve(
Artist::class,
Artist::class . '.queryBuilder',
),
],
],
]),
]);
To listen for this event and add filtering, such as filtering for the context user, create a listener.
use ApiSkeletons\Doctrine\ORM\GraphQL\Event\QueryBuilder;
use League\Event\EventDispatcher;
$driver->get(EventDispatcher::class)->subscribeTo(Artist::class . '.queryBuilder',
function(QueryBuilder $event) {
$event->getQueryBuilder()
->innerJoin('entity.user', 'user') // The default entity alias is always `entity`
->andWhere($event->getQueryBuilder()->expr()->eq('user.id', ':userId'))
->setParameter('userId', $event->getContext()['user']->getId())
;
}
);
Functions of the QueryBuilder
event in addition to getters for
all resolve parameters:
getQueryBuilder
- Will return a query builder with the user specified
filters already applied.
getOffset
- Will return the offset for the query. The QueryBuilder passed
to the event is not modified with the offset and limit yet. So if you have a
large dataset and need to fetch it within the event, you may use this method
to get the offset.
getLimit
- Will return the limit for the query. The QueryBuilder passed
to the event is not modified with the offset and limit yet. So if you have a
large dataset and need to fetch it within the event, you may use this method
to get the limit.
When an association is resolved from an entity or another association, you may listen to the Criteria Event to add additional criteria for filtering the collection if you assign an event name in the attributes.
Note that pagination limits are not applied to the collection before this event is fired. That way you can add additional criteria to the collection or filter the collection before the limit is applied.
This is done by fetching the collection within the event and running additional filters on each element. This is not the most efficient way to filter data, but it is the most flexible.
Two methods are supported for filtering the collection. You may add criteria to the Criteria object or you may fetch the collection and filter it directly. When you filter the collection directly you must use the setCollection method to update the collection on the event.
Using the Criteria object is the most efficient way to filter the collection.
use ApiSkeletons\Doctrine\ORM\GraphQL\Attribute as GraphQL;
use ApiSkeletons\Doctrine\ORM\GraphQL\Event\Criteria;
use App\ORM\Entity\Artist;
use League\Event\EventDispatcher;
#[GraphQL\Entity]
class Artist
{
#[GraphQL\Field]
public $id;
#[GraphQL\Field]
public $name;
#[GraphQL\Association(criteriaEventName: self::class . '.performances.criteria')]
public $performances;
}
// Add a listener to your driver to filter with a criteria object
$driver->get(EventDispatcher::class)->subscribeTo(
Artist::class . '.performances.criteria',
function (Criteria $event): void {
$event->getCriteria()->andWhere(
$event->getCriteria()->expr()->eq('isDeleted', false)
);
},
);
// Add a listener to your driver to filter with a collection filter
$driver->get(EventDispatcher::class)->subscribeTo(
Artist::class . '.performances.criteria',
function (Criteria $event): void {
// Match the collection with the criteria FIRST
$matchingCollection = $event->getCollection()->matching($event->getCriteria());
// Then filter the collection
$event->setCollection($matchingCollection->filter(
static function ($performance) {
return $performance->getIsDeleted() === false;
}
));
},
);
The Criteria
event has two functions in addition to getters for
all resolve parameters:
getCriteria
- Will return a Criteria object with the user specified
filters already applied.
getCollection
- Will return the unfetched collection object. This is useful
if you need to fetch the collection to apply additional criteria.
setCollection
- Will set the collection object. This is useful if you
need to filter the collection directly.
getOffset
- Will return the projected offset for the collection. The collection passed
to the event is not modified with the offset and limit yet. So if you have a
large dataset and need to fetch it within the event, you may use this method
to get the expected offset.
getLimit
- Will return the projectd limit for the collection. The collection passed
to the event is not modified with the offset and limit yet. So if you have a
large dataset and need to fetch it within the event, you may use this method
to get the expected limit.
Note
The offset and limit is calculated before this event is fired and calculated again after the event. This is because the collection may be fetched and filtered before the limit is applied. The offset and limit are recalculated after the event is fired to ensure the correct data is returned.
You may modify the array used to define an entity type before it is created. This can be used for generated data and the like. You must attach to events before defining your GraphQL schema.
There are two ways to extend an entity type. You can extend an entity
by listening to the EntityDefinition
event. You can extend an entity
by creating a new entity type by using a custom event name to replace the default.
The EntityDefinition
event is dispatched when an entity type is created.
The default name for this event is Entity::class . '.definition'
. All entity
types for Entity::class
will be affected by this event.
The $driver->type()
method takes a second, optional, event name parameter.
When it is called with an event name, the event will replace the default
Entity::class . '.definition'
dispatched when the
entity type is created. The type name in GraphQL will be the entity name
with the event name appended.
use ApiSkeletons\Doctrine\ORM\GraphQL\Driver;
use ApiSkeletons\Doctrine\ORM\GraphQL\Event\EntityDefinition;
use App\ORM\Entity\Artist;
use GraphQL\Type\Definition\ResolveInfo;
use League\Event\EventDispatcher;
$driver = new Driver($entityManager);
$driver->get(EventDispatcher::class)->subscribeTo(
Artist::class . '.definition',
static function (EntityDefinition $event): void {
$definition = $event->getDefinition();
// In order to modify the fields you must resolve the closure
$fields = $definition['fields']();
// Add a custom field to show the name without a prefix of 'The'
$fields['nameUnprefix'] = [
'type' => Type::string(),
'description' => 'A computed dynamically added field',
'resolve' => static function ($objectValue, array $args, $context, ResolveInfo $info): mixed {
return trim(str_replace('The', '', $objectValue->getName()));
},
];
$definition['fields'] = $fields;
}
);
The EntityDefinition
event has one function:
getDefinition
- Will return an ArrayObject with the ObjectType definition.
Because this is an ArrayObject you may manipulate it as
needed and the value is set by reference, just like the
QueryBuilder event above.
A clever use of this event is to add a new field for related data and specify
a custom QueryBuilder event in the $driver->resolve()
function.
You may specify a custom event name for a an entity type. This is useful to create one-off entity objects that need special handling. For instance, if you want to append a field to an entity for only a single query, but not globally for all instances of the entity class.
$driver->type(Entity::class, Entity::class . '.entityDefinitionEvent');
You may modify the metadata directly when built. This event must be subscribed to immediately after creating the driver. See Metadata documentation.
This event is named 'metadata.build'
.
use ApiSkeletons\Doctrine\ORM\GraphQL\Driver;
use ApiSkeletons\Doctrine\ORM\GraphQL\Event\Metadata;
use App\ORM\Entity\Performance;
use League\Event\EventDispatcher;
$driver = new Driver($entityManager);
$driver->get(EventDispatcher::class)->subscribeTo(
'metadata.build',
static function (Metadata $event): void {
$metadata = $event->getMetadata();
$metadata[Performance::class]['limit'] = 100;
},
);
The BuildMetadata
event has one function:
getMetadata
- Will return an ArrayObject with the metadata.
Because this is an ArrayObject you may manipulate it as
needed and the value is set by reference, just like the
QueryBuilder event above.
This is documentation for API-Skeletons/doctrine-orm-graphql. Please add your ★ star to the project.
Authored by API Skeletons.