-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve documentation on dataLoader using GraphQlBundle #20
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,85 +90,197 @@ Here the list of existing promise adapters: | |
This bundle can be use with [GraphQLBundle](https://github.com/overblog/GraphQLBundle). | ||
Here an example: | ||
|
||
* Bundle config | ||
* First create your service. We will use the Webonyx promise adapter | ||
|
||
```yaml | ||
#graphql | ||
#config/services.yaml | ||
services: | ||
graphql_promise_adapter: | ||
class: Overblog\DataLoader\Promise\Adapter\Webonyx\GraphQL\SyncPromiseAdapter | ||
public: true | ||
|
||
|
||
### | ||
# Force the overblog promise adapter to use the webonyx adapter | ||
### | ||
Overblog\PromiseAdapter\PromiseAdapterInterface: | ||
class: Overblog\PromiseAdapter\Adapter\WebonyxGraphQLSyncPromiseAdapter | ||
arguments: | ||
- "@graphql_promise_adapter" | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems very verbose, this was the only configuration that I needed: Overblog\DataLoader\Promise\Adapter\Webonyx\GraphQL\SyncPromiseAdapter: ~
Overblog\PromiseAdapter\Adapter\WebonyxGraphQLSyncPromiseAdapter: ~
Overblog\PromiseAdapter\PromiseAdapterInterface: '@Overblog\PromiseAdapter\Adapter\WebonyxGraphQLSyncPromiseAdapter' Why not just register this out of the box? |
||
### | ||
# Add magic configuration to load all your dataLoader | ||
### | ||
App\Loader\: | ||
resource: "../src/Loader/*" | ||
``` | ||
|
||
* Now configure the packages | ||
|
||
```yaml | ||
#config/packages/graphql.yaml | ||
overblog_graphql: | ||
definitions: | ||
schema: | ||
query: Query | ||
#[...] | ||
services: | ||
promise_adapter: "webonyx_graphql.sync_promise_adapter" | ||
promise_adapter: "graphql_promise_adapter" | ||
|
||
#dataloader | ||
#config/packages/dataloader.yaml | ||
overblog_dataloader: | ||
defaults: | ||
promise_adapter: "overblog_dataloader.webonyx_graphql_sync_promise_adapter" | ||
loaders: | ||
ships: | ||
alias: "ships_loader" | ||
batch_load_fn: "@app.graph.ships_loader:all" | ||
promise_adapter: "graphql_promise_adapter" | ||
``` | ||
|
||
* Batch loader function | ||
* Create an abstract class GenericDataLoader | ||
|
||
```yaml | ||
services: | ||
app.graph.ship_repository: | ||
class: AppBundle\Entity\Repository\ShipRepository | ||
factory: ["@doctrine.orm.entity_manager", getRepository] | ||
arguments: | ||
- AppBundle\Entity\Ship | ||
```php | ||
<?php | ||
|
||
app.graph.ships_loader: | ||
class: AppBundle\GraphQL\Loader\ShipLoader | ||
arguments: | ||
- "@overblog_graphql.promise_adapter" | ||
- "@app.graph.ship_repository" | ||
namespace App\Loader; | ||
|
||
use Overblog\DataLoader\DataLoader; | ||
use Overblog\PromiseAdapter\PromiseAdapterInterface; | ||
|
||
abstract class AbstractDataLoader extends DataLoader | ||
{ | ||
/** | ||
* @param PromiseAdapterInterface $promiseAdapter | ||
*/ | ||
public function __construct(PromiseAdapterInterface $promiseAdapter) { | ||
parent::__construct( | ||
function ($ids) use ($promiseAdapter) { | ||
return $promiseAdapter->createAll($this->find($ids)); | ||
}, | ||
$promiseAdapter | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So you are creating a callable? The batch_load_fn option is supposed to take care of this. Did this ever work? @mcg-web we're a bit lost I think. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see my example here #22 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I went around the barn the long way, and concluded that while the original example isn't terrific, this new one is doing it wrong. You are not supposed to inherit from Dataloader -- this bundle will create services which are instances of Dataloader, and those services will call the callable you define in "batch_load_fn". |
||
); | ||
} | ||
|
||
/** | ||
* @param array $ids | ||
* | ||
* @return array | ||
*/ | ||
abstract protected function find(array $ids): array; | ||
} | ||
``` | ||
|
||
* Now create a DataLoader for all your entities. For example user | ||
|
||
```php | ||
<?php | ||
|
||
namespace AppBundle\GraphQL\Loader; | ||
namespace App\Loader; | ||
|
||
use AppBundle\Entity\Repository\ShipRepository; | ||
use GraphQL\Executor\Promise\PromiseAdapter; | ||
use App\Repository\UserRepository; | ||
use Overblog\PromiseAdapter\PromiseAdapterInterface; | ||
|
||
class ShipLoader | ||
class UserDataLoader extends AbstractDataLoader | ||
{ | ||
private $promiseAdapter; | ||
|
||
private $repository; | ||
|
||
public function __construct(PromiseAdapter $promiseAdapter, ShipRepository $repository) | ||
{ | ||
$this->promiseAdapter = $promiseAdapter; | ||
$this->repository = $repository; | ||
/** | ||
* @var UserRepository | ||
*/ | ||
private $userRepository; | ||
|
||
/** | ||
* @param UserRepository $userRepository | ||
* @param PromiseAdapterInterface $promiseAdapter | ||
*/ | ||
public function __construct( | ||
UserRepository $userRepository, | ||
PromiseAdapterInterface $promiseAdapter | ||
) { | ||
parent::__construct($promiseAdapter); | ||
$this->userRepository = $userRepository; | ||
} | ||
|
||
public function all(array $shipsIDs) | ||
/** | ||
* @inheritdoc | ||
*/ | ||
protected function find(array $ids): array | ||
{ | ||
$qb = $this->repository->createQueryBuilder('s'); | ||
$qb->add('where', $qb->expr()->in('s.id', ':ids')); | ||
$qb->setParameter('ids', $shipsIDs); | ||
$ships = $qb->getQuery()->getResult(); | ||
|
||
return $this->promiseAdapter->all($ships); | ||
return $this->userRepository->findById($ids); | ||
} | ||
} | ||
``` | ||
``` | ||
|
||
* Usage in a resolver | ||
|
||
```php | ||
public function resolveShip($shipID) | ||
<?php | ||
|
||
namespace App\Resolver; | ||
|
||
use App\Entity\User; | ||
use App\Loader\UserDataLoader; | ||
use App\Repository\UserRepository; | ||
use Overblog\GraphQLBundle\Definition\Argument; | ||
use Overblog\GraphQLBundle\Definition\Resolver\AliasedInterface; | ||
use Overblog\GraphQLBundle\Definition\Resolver\ResolverInterface; | ||
use Overblog\GraphQLBundle\Relay\Connection\Paginator; | ||
|
||
final class UserResolver implements ResolverInterface, AliasedInterface | ||
{ | ||
/** | ||
* @var UserDataLoader | ||
*/ | ||
private $userDataLoader; | ||
|
||
/** | ||
* @var UserRepository | ||
*/ | ||
private $userRepository; | ||
|
||
/** | ||
* @param UserDataLoader $userDataLoader | ||
* @param UserRepository $userRepository | ||
*/ | ||
public function __construct( | ||
UserDataLoader $userDataLoader, | ||
UserRepository $userRepository | ||
) { | ||
$this->userDataLoader = $userDataLoader; | ||
$this->userRepository = $userRepository; | ||
} | ||
|
||
/** | ||
* @param int $id | ||
* | ||
* @return User | ||
*/ | ||
public function resolveEntity(int $id) | ||
{ | ||
$promise = $this->container->get('ships_loader')->load($shipID); | ||
return $this->userDataLoader->load($id); | ||
} | ||
|
||
/** | ||
* @param Argument $args | ||
* | ||
* @return object|\Overblog\GraphQLBundle\Relay\Connection\Output\Connection | ||
* @throws \Exception | ||
*/ | ||
public function resolveList(Argument $args) | ||
{ | ||
$paginator = new Paginator(function ($offset, $limit) { | ||
$ids = $this->userRepository->paginatedUsersIds($offset, $limit); | ||
|
||
return $this->userDataLoader->loadMany($ids); | ||
}, Paginator::MODE_PROMISE); | ||
|
||
return $promise; | ||
return $paginator->auto($args, function() { | ||
return $this->userRepository->count([]); | ||
}); | ||
} | ||
|
||
/** | ||
* {@inheritdoc} | ||
*/ | ||
public static function getAliases(): array | ||
{ | ||
return [ | ||
'resolveEntity' => 'User', | ||
'resolveList' => 'Users', | ||
]; | ||
} | ||
} | ||
``` | ||
|
||
This is an example using the sync promise adapter of Webonyx. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does not have to be public.