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

Feature/documentation provider #16

Merged
Merged
Show file tree
Hide file tree
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
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