Skip to content

Commit

Permalink
Merge pull request #16 from TomHAnderson/feature/documenatation-provider
Browse files Browse the repository at this point in the history
Feature/documentation provider
  • Loading branch information
TomHAnderson authored Jun 28, 2018
2 parents e67e336 + e71cc27 commit 9383524
Show file tree
Hide file tree
Showing 16 changed files with 233 additions and 36 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ This library resolves relationships in Doctrine to provide full GraphQL
querying of specified resources and all related entities.
Entity metadata is introspected and is therefore Doctrine data driver agnostic.
Data is collected via hydrators thereby
allowing full control over each field using hydrator filters and strategies.
Multiple object managers are supported.
Multiple hydrator configurations are supported.
allowing full control over each field using hydrator filters and strategies and naming strategies.
Multiple object managers are supported. Multiple hydrator configurations are supported.
Works with GraphiQL.

Doctrine provides easy taversal of your database. Consider the following imaginary query:
```php
Expand Down
20 changes: 20 additions & 0 deletions docs/graphiql.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
GraphiQL and Documentation
==========================

Support for GraphiQL was added in v1.0.5 along with support for introspection queries.

The documentation for each field is created with a DocumentationProvider. Included
with zf-doctrine-graphql is an ApigilityDocumentationProvider. If you have need for
another form of documentation provider please create an issue on github. The more
included providers the merrier.



.. role:: raw-html(raw)
:format: html

.. note::
Authored by `API Skeletons <https://apiskeletons.com>`_. All rights reserved.


:raw-html:`<script async src="https://www.googletagmanager.com/gtag/js?id=UA-64198835-4"></script><script>window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}gtag('js', new Date());gtag('config', 'UA-64198835-4');</script>`
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ schema, if you wish, allowing deep GraphQL queries on your data with a single en
queries
configuration
events
graphiql
internals


Expand Down
2 changes: 1 addition & 1 deletion docs/queries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Provided Filters::
fieldName_lte - Less Than or Equal To
fieldName_in - Filter for values in an array
fieldName_notin - Filter for values not in an array
fieldName_between - Fiilter between `from` and `to` values. Good substitute for DateTime Equals.
fieldName_between - Filter between `from` and `to` values. Good substitute for DateTime Equals.
fieldName_contains - Strings only. Similar to a Like query as `like '%value%'`
fieldName_startswith - Strings only. A like query from the beginning of the value `like 'value%'`
fieldName_endswith - Strings only. A like query from the end of the value `like '%value'`
Expand Down
5 changes: 5 additions & 0 deletions src/ConfigProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public function getDependencyConfig()
return [
'aliases' => [
'ZF\Doctrine\GraphQL\Hydrator\HydratorExtractTool' => Hydrator\HydratorExtractToolDefault::class,
'ZF\Doctrine\GraphQL\Documentation\DocumentationProvider'
=> Documentation\HydratorConfigurationDocumentationProvider::class,
],
'factories' => [
Hydrator\HydratorExtractToolDefault::class => Hydrator\HydratorExtractToolDefaultFactory::class,
Expand All @@ -52,6 +54,9 @@ public function getDependencyConfig()
Resolve\Loader::class => Resolve\LoaderFactory::class,
Type\Loader::class => Type\LoaderFactory::class,
Type\TypeManager::class => Type\TypeManagerFactory::class,

Documentation\HydratorConfigurationDocumentationProvider::class =>
Documentation\HydratorConfigurationDocumentationProviderFactory::class,
],
];
}
Expand Down
4 changes: 4 additions & 0 deletions src/Console/ConfigurationSkeletonController.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ public function indexAction()

$strategies = [];
$filters = [];
$documentation = ['_entity' => ''];
foreach ($classMetadata->getAssociationNames() as $associationName) {
$documentation[$associationName] = '';
$mapping = $classMetadata->getAssociationMapping($associationName);

// See comment on NullifyOwningAssociation for details of why this is done
Expand All @@ -58,6 +60,7 @@ public function indexAction()
}

foreach ($classMetadata->getFieldNames() as $fieldName) {
$documentation[$fieldName] = '';
$fieldMetadata = $classMetadata->getFieldMapping($fieldName);

// Handle special named fields
Expand Down Expand Up @@ -108,6 +111,7 @@ public function indexAction()
'hydrator' => null,
'strategies' => $strategies,
'filters' => $filters,
'documentation' => $documentation,
];
}
}
Expand Down
42 changes: 25 additions & 17 deletions src/Criteria/FilterTypeAbstractFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public function __invoke(ContainerInterface $container, $requestedName, array $o
$fields[$fieldName . '_sort'] = [
'name' => $fieldName . '_sort',
'type' => Type::string(),
'description' => 'building...',
'description' => 'Sort the result either ASC or DESC',
];
}

Expand All @@ -102,67 +102,71 @@ public function __invoke(ContainerInterface $container, $requestedName, array $o
$fields[$fieldName] = [
'name' => $fieldName,
'type' => $graphQLType,
'description' => 'building...',
'description' => 'Equals; same as name: value. DateTime not supported.',
];

$fields[$fieldName . '_eq'] = [
'name' => $fieldName . '_eq',
'type' => $graphQLType,
'description' => 'building...',
'description' => 'Equals; same as name: value. DateTime not supported.',
];
}

if ($filterManager->has('neq')) {
$fields[$fieldName . '_neq'] = [
'name' => $fieldName . '_neq',
'type' => $graphQLType,
'description' => 'building...',
'description' => 'Not Equals',
];
}

if ($filterManager->has('lt')) {
$fields[$fieldName . '_lt'] = [
'name' => $fieldName . '_lt',
'type' => $graphQLType,
'description' => 'building...',
'description' => 'Less Than',
];
}

if ($filterManager->has('lte')) {
$fields[$fieldName . '_lte'] = [
'name' => $fieldName . '_lte',
'type' => $graphQLType,
'description' => 'building...',
'description' => 'Less Than or Equal To',
];
}

if ($filterManager->has('gt')) {
$fields[$fieldName . '_gt'] = [
'name' => $fieldName . '_gt',
'type' => $graphQLType,
'description' => 'building...',
'description' => 'Greater Than',
];
}

if ($filterManager->has('gte')) {
$fields[$fieldName . '_gte'] = [
'name' => $fieldName . '_gte',
'type' => $graphQLType,
'description' => 'building...',
'description' => 'Greater Than or Equal To',
];
}

if ($filterManager->has('eq') && $filterManager->has('neq')) {
$fields[$fieldName . '_isnull'] = [
'name' => $fieldName . '_isnull',
'type' => Type::boolean(),
'description' => 'building...',
'description' => 'Takes a boolean. If TRUE return results where the field is null. '
. 'If FALSE returns results where the field is not null. '
. 'NOTE: acts as "isEmpty" for collection filters. A value of false will '
. 'be handled as though it were null.',
];
}

if ($filterManager->has('lte') && $filterManager->has('gte')) {
$fields[$fieldName . '_between'] = [
'name' => $fieldName . '_between',
'description' => 'Filter between `from` and `to` values. Good substitute for DateTime Equals.',
'type' => new FilterTypeNS\Between(['fields' => [
'from' => [
'name' => 'from',
Expand All @@ -181,15 +185,15 @@ public function __invoke(ContainerInterface $container, $requestedName, array $o
$fields[$fieldName . '_in'] = [
'name' => $fieldName . '_in',
'type' => Type::listOf($graphQLType),
'description' => 'building...',
'description' => 'Filter for values in an array',
];
}

if ($filterManager->has('notin')) {
$fields[$fieldName . '_notin'] = [
'name' => $fieldName . '_notin',
'type' => Type::listOf($graphQLType),
'description' => 'building...',
'description' => 'Filter for values not in an array',
];
}

Expand All @@ -198,23 +202,25 @@ public function __invoke(ContainerInterface $container, $requestedName, array $o
$fields[$fieldName . '_startswith'] = [
'name' => $fieldName . '_startswith',
'type' => $graphQLType,
'description' => 'building...',
'documentation' => 'Strings only. '
. 'A like query from the beginning of the value `like \'value%\'`',
];
}

if ($filterManager->has('endswith')) {
$fields[$fieldName . '_endswith'] = [
'name' => $fieldName . '_endswith',
'type' => $graphQLType,
'description' => 'building...',
'documentation' => 'Strings only. '
. 'A like query from the end of the value `like \'%value\'`',
];
}

if ($filterManager->has('contains')) {
$fields[$fieldName . '_contains'] = [
'name' => $fieldName . '_contains',
'type' => $graphQLType,
'description' => 'building...',
'description' => 'Strings only. Similar to a Like query as `like \'%value%\'`',
];
}
}
Expand All @@ -223,29 +229,31 @@ public function __invoke(ContainerInterface $container, $requestedName, array $o
$fields[$fieldName . '_memberof'] = [
'name' => $fieldName . '_memberof',
'type' => $graphQLType,
'description' => 'building...',
'description' => 'Matches a value in an array field.',
];
}
}

$fields[$fieldName . '_distinct'] = [
'name' => $fieldName . '_distinct',
'type' => Type::boolean(),
'description' => 'building...',
'description' => 'Return a unique list of fieldName. Only one distinct fieldName allowed per filter.',
];
}

$fields['_skip'] = [
'name' => '_skip',
'type' => Type::int(),
'documentation' => 'Skip forward x records from beginning of data set.',
];
$fields['_limit'] = [
'name' => '_limit',
'type' => Type::int(),
'documentation' => 'Limit the number of results to x.',
];

$instance = new FilterType([
'name' => str_replace('\\', '_', $requestedName) . 'CriteriaFilter',
'name' => str_replace('\\', '_', $requestedName) . '__CriteriaFilter',
'fields' => function () use ($fields) {
return $fields;
},
Expand Down
58 changes: 58 additions & 0 deletions src/Documentation/ApigilityDocumentationProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

namespace ZF\Doctrine\GraphQL\Documentation;

/**
* @codeCoverageIgnore
*/
class ApigilityDocumentationProvider implements
DocumentationProviderInterface
{
protected $config;

public function __construct(array $config)
{
$this->config = $config;
}

public function getEntity($entityName, array $options)
{
// Documentation for entities is stored in the documentation.php config file.
// Fetching all those files is outside the scope of work for this class for now.
return 'Doctrine Entity ' . $entityName;
}

/**
* Populate the field documentation based on teh input filter
* for the first matching entity found in zf-rest configuration
*/
public function getField($entityName, $fieldName, array $options)
{
$inputFilter = null;
$description = null;

if (! isset($this->config['zf-rest'])) {
return null;
}

foreach ($this->config['zf-rest'] as $controllerName => $restConfig) {
if ($restConfig['entity_class'] == $entityName) {
$inputFilter = $this->config['zf-content-validation'][$controllerName]['input_filter'] ?? null;
break;
}
}

if ($inputFilter
&& isset($this->config['input_filter_specs'])
&& isset($this->config['input_filter_specs'][$inputFilter])) {
foreach ($this->config['input_filter_specs'][$inputFilter] as $fieldConfig) {
if ($fieldConfig['name'] == $fieldName) {
$description = $fieldConfig['description'] ?? null;
break;
}
}
}

return $description;
}
}
21 changes: 21 additions & 0 deletions src/Documentation/ApigilityDocumentationProviderFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace ZF\Doctrine\GraphQL\Documentation;

use Interop\Container\ContainerInterface;

/**
* @codeCoverageIgnore
*/
class ApigilityDocumentationProviderFactory
{
public function __invoke(
ContainerInterface $container,
$requestedName,
array $options = null
) {
$config = $container->get('config');

return new ApigilityDocumentationProvider($config);
}
}
9 changes: 9 additions & 0 deletions src/Documentation/DocumentationProviderInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace ZF\Doctrine\GraphQL\Documentation;

interface DocumentationProviderInterface
{
public function getField($entityClassName, $fieldName, array $config);
public function getEntity($entityClassName, array $config);
}
30 changes: 30 additions & 0 deletions src/Documentation/HydratorConfigurationDocumentationProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace ZF\Doctrine\GraphQL\Documentation;

class HydratorConfigurationDocumentationProvider implements
DocumentationProviderInterface
{
protected $config;

public function __construct(array $config)
{
$this->config = $config;
}

public function getEntity($entityClassName, array $options)
{
$hydratorAlias = 'ZF\\Doctrine\\GraphQL\\Hydrator\\' . str_replace('\\', '_', $entityClassName);
$config = $this->config['zf-doctrine-graphql-hydrator'][$hydratorAlias][$options['hydrator_section']] ?? null;

return $config['documentation']['_entity'] ?? null;
}

public function getField($entityClassName, $fieldName, array $options)
{
$hydratorAlias = 'ZF\\Doctrine\\GraphQL\\Hydrator\\' . str_replace('\\', '_', $entityClassName);
$config = $this->config['zf-doctrine-graphql-hydrator'][$hydratorAlias][$options['hydrator_section']] ?? null;

return $config['documentation'][$fieldName] ?? null;
}
}
Loading

0 comments on commit 9383524

Please sign in to comment.