Skip to content
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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 160 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

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.



###
# Force the overblog promise adapter to use the webonyx adapter
###
Overblog\PromiseAdapter\PromiseAdapterInterface:
class: Overblog\PromiseAdapter\Adapter\WebonyxGraphQLSyncPromiseAdapter
arguments:
- "@graphql_promise_adapter"

Copy link
Contributor

Choose a reason for hiding this comment

The 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

Choose a reason for hiding this comment

The 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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my example here #22

Choose a reason for hiding this comment

The 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.
Expand Down