diff --git a/README.md b/README.md index 1862267..5facd21 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,10 @@ with following configuration (`hasMany`) : - "after" : cursor-based navigation ``` +I case of relationships and if `StudioNet\GraphQL\Support\Type\Meta` type is +registered, you'll be granted to use field like `_posts_meta { count }` in order +to retrieve global count. + This transformer also converts [mutated fields from model](https://laravel.com/docs/5.4/eloquent-mutators). Let's show you the convertion mapping (based on supported model cast) : @@ -215,6 +219,17 @@ Generate singular query based on `EloquentObjectType`. - "id" : id-based navigation ``` +#### `StudioNet\GraphQL\Generator\Query\MetaEloquentGenerator` + +Generate meta query based on `EloquentObjectType`. + +``` +- Type : "EloquentObjectType" +- Return : [ + - "count" : count of objects +] +``` + #### `StudioNet\GraphQL\Generator\Query\NodesEloquentGenerator` Generate pluralized query based on `EloquentObjectType`. @@ -455,3 +470,41 @@ query { } } ``` + +#### Using metadata + +```php +# config/graphql.php + +return [ + 'type' => [ + \StudioNet\GraphQL\Support\Type\Meta::class, + \App\User::class, + \App\Post::class + ] +]; +``` + +```graphql +query { + _users_meta { + count + } + + users (take: 2) { + id + first_name + last_name + + posts (take: 5) { + id + title + content + } + + _posts_meta { + count + } + } +} +``` diff --git a/resources/config.php b/resources/config.php index ea7d443..8f686f0 100644 --- a/resources/config.php +++ b/resources/config.php @@ -18,8 +18,14 @@ ], // Type configuration. You can append any data : a transformer will handle - // them (if exists) - 'type' => [], + // them (if exists). Order matter + 'type' => [ + // Mandatory in order to make working meta data + \StudioNet\GraphQL\Support\Type\Meta::class + + // Custom types + // \App\User::class + ], // Scalar field definitions 'scalar' => [ @@ -57,6 +63,7 @@ // all will be called 'generator' => [ 'query' => [ + \StudioNet\GraphQL\Generator\Query\MetaEloquentGenerator::class, \StudioNet\GraphQL\Generator\Query\NodeEloquentGenerator::class, \StudioNet\GraphQL\Generator\Query\NodesEloquentGenerator::class ], diff --git a/src/Generator/Generator.php b/src/Generator/Generator.php index a78220a..f7718a2 100644 --- a/src/Generator/Generator.php +++ b/src/Generator/Generator.php @@ -21,4 +21,11 @@ abstract class Generator implements GeneratorInterface { public function __construct(Application $app) { $this->app = $app; } + + /** + * {@inheritDoc} + */ + public function dependsOn() { + return []; + } } diff --git a/src/Generator/GeneratorInterface.php b/src/Generator/GeneratorInterface.php index a998f33..ef32db0 100644 --- a/src/Generator/GeneratorInterface.php +++ b/src/Generator/GeneratorInterface.php @@ -28,4 +28,12 @@ public function generate($instance); * @return string */ public function getKey($instance); + + /** + * Assert a specific type is registered before executing. If not, the + * generator will not be triggered + * + * @return array + */ + public function dependsOn(); } diff --git a/src/Generator/Query/MetaEloquentGenerator.php b/src/Generator/Query/MetaEloquentGenerator.php new file mode 100644 index 0000000..48a0769 --- /dev/null +++ b/src/Generator/Query/MetaEloquentGenerator.php @@ -0,0 +1,70 @@ +name))); + } + + /** + * {@inheritDoc} + */ + public function dependsOn() { + return ['meta']; + } + + /** + * {@inheritDoc} + */ + public function generate($instance) { + return [ + 'type' => $this->app['graphql']->type('meta'), + 'resolve' => $this->getResolver($instance->getModel()) + ]; + } + + /** + * Return meta resolver + * + * @param Model $model + * @return callable + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + public function getResolver(Model $model) { + return function($root, array $args, $context, ResolveInfo $info) use ($model) { + // Clone collection in order to not erase existin collection + $collection = $model->newQuery(); + $fields = $info->getFieldSelection(3); + $data = []; + + foreach (array_keys($fields) as $key) { + switch ($key) { + case 'count' : $data['count'] = $collection->count(); break; + } + } + + return $data; + }; + } +} diff --git a/src/GraphQL.php b/src/GraphQL.php index d35bef4..bcca647 100644 --- a/src/GraphQL.php +++ b/src/GraphQL.php @@ -341,7 +341,21 @@ public function registerGenerator($category, $generator) { throw new Exception\GeneratorException('Unable to find given category'); } - $this->generators[$category][] = $this->app->make($generator); + $generator = $this->app->make($generator); + $dependencies = $generator->dependsOn(); + + // Assert generator has all is types dependencies + if (!empty($dependencies)) { + foreach ($dependencies as $dependency) { + $dependency = strtolower($dependency); + + if (!array_key_exists($dependency, $this->types)) { + return; + } + } + } + + $this->generators[$category][] = $generator; } /** diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 3d4a8da..dafdd2d 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -27,9 +27,9 @@ public function boot() { // Call external methods to load defined schemas and others things $this->registerScalars(); $this->registerTransformers(); - $this->registerGenerators(); $this->registerSchemas(); $this->registerTypes(); + $this->registerGenerators(); } /** diff --git a/src/Support/Type/Meta.php b/src/Support/Type/Meta.php new file mode 100644 index 0000000..db9c75d --- /dev/null +++ b/src/Support/Type/Meta.php @@ -0,0 +1,35 @@ + ['type' => GraphQLType::nonNull(GraphQLType::int())], + ]; + } +} diff --git a/src/Transformer/Type/ModelTransformer.php b/src/Transformer/Type/ModelTransformer.php index 74600e3..5673904 100644 --- a/src/Transformer/Type/ModelTransformer.php +++ b/src/Transformer/Type/ModelTransformer.php @@ -1,6 +1,8 @@ hasMeta()) { + // Create a field named `_{column}_meta` in order to + // access info from it (like global count) + $fields[] = [ + 'name' => "_{$column}_meta", + 'description' => "{$column} metadata", + 'type' => $this->getMeta(), + 'resolve' => $this->getMetaResolver($relation) + ]; + } + + // Continue throw process $type = GraphQLType::listOf($type); $field = array_merge([ 'args' => $this->getArguments(), @@ -147,6 +159,28 @@ private function getFields(Model $model) { }; } + /** + * Check if meta is global registered + * + * @return bool + */ + private function hasMeta() { + try { + return (bool) $this->app['graphql']->type('meta'); + } catch (\Exception $e) {} + + return false; + } + + /** + * Return meta type + * + * @return ObjectType + */ + private function getMeta() { + return $this->app['graphql']->type('meta'); + } + /** * Resolve a relationship field * @@ -180,6 +214,32 @@ private function getResolver(array $relation) { }; } + /** + * Resolve a meta field + * + * @param array $relation + * @return callable + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + */ + private function getMetaResolver(array $relation) { + $method = $relation['field']; + + return function($root, array $args, $context, ResolveInfo $info) use ($method) { + // Clone collection in order to not erase existin collection + $collection = clone $root->{$method}(); + $fields = $info->getFieldSelection(3); + $data = []; + + foreach (array_keys($fields) as $key) { + switch ($key) { + case 'count' : $data['count'] = $collection->count(); break; + } + } + + return $data; + }; + } + /** * Return available arguments (many because there's no argument for single * element). Order matter diff --git a/src/Transformer/TypeTransformer.php b/src/Transformer/TypeTransformer.php index 948a046..3a51b7c 100644 --- a/src/Transformer/TypeTransformer.php +++ b/src/Transformer/TypeTransformer.php @@ -39,8 +39,8 @@ public function transform($instance) { // Merge all attributes within attributes var $attributes = array_merge($attributes, [ 'fields' => $fields, - 'name' => $this->getName(), - 'description' => $this->getDescription() + 'name' => $instance->getName(), + 'description' => $instance->getDescription() ]); if (!empty($interfaces)) { @@ -64,7 +64,7 @@ private function getFieldResolver(TypeInterface $type, $name, array $field) { return $field['resolve']; } - $method = studly_case(sprintf('resolve-%s-%field', $name)); + $method = studly_case(sprintf('resolve-%s-field', $name)); if (method_exists($type, $method)) { return function() use ($type, $method) { return [$type, $method]; };