diff --git a/Command/GeneratePlatformSchemaCommand.php b/Command/GeneratePlatformSchemaCommand.php index 1915aff..67e69de 100644 --- a/Command/GeneratePlatformSchemaCommand.php +++ b/Command/GeneratePlatformSchemaCommand.php @@ -26,7 +26,7 @@ class GeneratePlatformSchemaCommand extends Command */ private $generator; - const TYPES_DIRECTORY = "src/AppBundle/Resources/config/graphql"; + const TYPES_DIRECTORY = "app/config/graphql"; public function __construct(Repository $repository, SchemaGenerator $generator) { diff --git a/DependencyInjection/BDEzPlatformGraphQLExtension.php b/DependencyInjection/BDEzPlatformGraphQLExtension.php index 180bd02..5d8e5d0 100644 --- a/DependencyInjection/BDEzPlatformGraphQLExtension.php +++ b/DependencyInjection/BDEzPlatformGraphQLExtension.php @@ -16,7 +16,7 @@ */ class BDEzPlatformGraphQLExtension extends Extension implements PrependExtensionInterface { - const DOMAIN_SCHEMA_FILE = __DIR__. '/../../../../src/AppBundle/Resources/config/graphql/Domain.types.yml'; + const DOMAIN_SCHEMA_FILE = __DIR__. '/../../../../app/config/graphql/Domain.types.yml'; /** * {@inheritdoc} diff --git a/DomainContent/NameHelper.php b/DomainContent/NameHelper.php index 1651568..6a763be 100644 --- a/DomainContent/NameHelper.php +++ b/DomainContent/NameHelper.php @@ -30,11 +30,17 @@ public function domainContentCollectionField(ContentType $contentType) { return $this->pluralize(lcfirst($this->toCamelCase($contentType->identifier))); } + public function domainContentName(ContentType $contentType) { return ucfirst($this->toCamelCase($contentType->identifier)) . 'Content'; } + public function domainContentConnection($contentType) + { + return ucfirst($this->toCamelCase($contentType->identifier)) . 'ContentConnection'; + } + public function domainContentTypeName(ContentType $contentType) { return ucfirst($this->toCamelCase($contentType->identifier)) . 'ContentType'; diff --git a/DomainContent/SchemaWorker/ContentType/AddDomainContentToDomainGroup.php b/DomainContent/SchemaWorker/ContentType/AddDomainContentToDomainGroup.php index c2f5728..8eeeb62 100644 --- a/DomainContent/SchemaWorker/ContentType/AddDomainContentToDomainGroup.php +++ b/DomainContent/SchemaWorker/ContentType/AddDomainContentToDomainGroup.php @@ -25,18 +25,21 @@ public function work(array &$schema, array $args) [$this->getGroupName($contentTypeGroup)] ['config']['fields'] [$this->getContentCollectionField($contentType)] = [ - 'type' => sprintf("[%s]", $this->getContentName($contentType)), - // @todo Improve description to mention that it is a collection ? + 'type' => $this->getNameHelper()->domainContentConnection($contentType), 'description' => isset($descriptions['eng-GB']) ? $descriptions['eng-GB'] : 'No description available', 'resolve' => sprintf( '@=resolver("DomainContentItemsByTypeIdentifier", ["%s", args])', $contentType->identifier ), + 'argsBuilder' => 'Relay::Connection', 'args' => [ 'query' => [ 'type' => "ContentSearchQuery", 'description' => "A Content query used to filter results" ], + 'sortBy' => [ + 'type' => 'SortByOptions' + ], ], ]; @@ -46,10 +49,11 @@ public function work(array &$schema, array $args) [$this->getContentField($contentType)] = [ 'type' => $this->getContentName($contentType), 'description' => isset($descriptions['eng-GB']) ? $descriptions['eng-GB'] : 'No description available', - 'resolve' => sprintf('@=resolver("DomainContentItem", [args, "%s"])', $contentType->identifier), + 'resolve' => sprintf( + '@=resolver("DomainContentItem", [args, "%s"])', + $contentType->identifier + ), 'args' => [ - // @todo How do we constraint this so that it only takes an id of an item of that type ? - // same approach than GlobalId ? (-) 'id' => [ 'type' => 'Int', 'description' => sprintf('A %s content id', $contentType->identifier), diff --git a/DomainContent/SchemaWorker/ContentType/DefineDomainContent.php b/DomainContent/SchemaWorker/ContentType/DefineDomainContent.php index abd4afd..a1d25b9 100644 --- a/DomainContent/SchemaWorker/ContentType/DefineDomainContent.php +++ b/DomainContent/SchemaWorker/ContentType/DefineDomainContent.php @@ -23,7 +23,7 @@ public function work(array &$schema, array $args) 'inherits' => ['AbstractDomainContent'], 'config' => [ 'fields' => [], - 'interfaces' => ['DomainContent'], + 'interfaces' => ['DomainContent', 'Node'], ] ]; @@ -35,6 +35,14 @@ public function work(array &$schema, array $args) 'fields' => [] ], ]; + + $schema[$this->getNameHelper()->domainContentConnection($contentType)] = [ + 'type' => 'relay-connection', + 'config' => [ + 'nodeType' => $this->getDomainContentName($contentType), + 'inherits' => ['DomainContentByIdentifierConnection'], + ] + ]; } public function canWork(array $schema, array $args) diff --git a/GraphQL/InputMapper/SearchQueryMapper.php b/GraphQL/InputMapper/SearchQueryMapper.php index e0d903c..cbe97f7 100644 --- a/GraphQL/InputMapper/SearchQueryMapper.php +++ b/GraphQL/InputMapper/SearchQueryMapper.php @@ -14,11 +14,18 @@ class SearchQueryMapper { /** + * @param array $inputArray * @return \eZ\Publish\API\Repository\Values\Content\Query */ public function mapInputToQuery(array $inputArray) { $query = new Query(); + if (isset($inputArray['offset'])) { + $query->offset = $inputArray['offset']; + } + if (isset($inputArray['limit'])) { + $query->limit = $inputArray['limit']; + } $criteria = []; if (isset($inputArray['ContentTypeIdentifier'])) { @@ -59,6 +66,10 @@ public function mapInputToQuery(array $inputArray) $query->filter = count($criteria) > 1 ? new Query\Criterion\LogicalAnd($criteria) : $criteria[0]; } + if (isset($inputArray['sortBy'])) { + $query->sortClauses = [new $inputArray['sortBy']]; + } + return $query; } diff --git a/GraphQL/Relay/DomainConnectionBuilder.php b/GraphQL/Relay/DomainConnectionBuilder.php new file mode 100644 index 0000000..bbb2e90 --- /dev/null +++ b/GraphQL/Relay/DomainConnectionBuilder.php @@ -0,0 +1,17 @@ +contentService = $contentService; + $this->typeResolver = $typeResolver; + $this->contentTypeService = $contentTypeService; + $this->nameHelper = $nameHelper; + } + + /** + * @param $globalId + * + * @return null|\eZ\Publish\API\Repository\Values\Content\ContentInfo + * + * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException + * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException + */ + public function resolveNode($globalId) + { + $params = GlobalId::fromGlobalId($globalId); + + if (in_array($params['type'], ['Content', 'DomainContent'])) { + return $this->contentService->loadContentInfo($params['id']); + } + + return null; + } + + /** + * @param $object + * + * @return \GraphQL\Type\Definition\Type + */ + public function resolveType($object) + { + if ($object instanceof ContentInfo) { + return $this->typeResolver->resolve( + $this->nameHelper->domainContentName( + $this->contentTypeService->loadContentType($object->contentTypeId) + ) + ); + } + } +} diff --git a/GraphQL/Relay/SearchResolver.php b/GraphQL/Relay/SearchResolver.php new file mode 100644 index 0000000..2b173cb --- /dev/null +++ b/GraphQL/Relay/SearchResolver.php @@ -0,0 +1,70 @@ +searchService = $searchService; + } + + /** + * @param $args + * @return Connection + * + * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException + */ + public function searchContent($args) + { + $queryArg = $args['query']; + + $query = new Query(); + $criteria = []; + + if (isset($queryArg['ContentTypeIdentifier'])) { + $criteria[] = new Query\Criterion\ContentTypeIdentifier($queryArg['ContentTypeIdentifier']); + } + + if (isset($queryArg['Text'])) { + foreach ($queryArg['Text'] as $text) { + $criteria[] = new Query\Criterion\FullText($text); + } + } + + if (count($criteria) === 0) { + return null; + } + $query->filter = count($criteria) > 1 ? new Query\Criterion\LogicalAnd($criteria) : $criteria[0]; + $searchResult = $this->searchService->findContentInfo($query); + + $contentItems = array_map( + function (SearchHit $hit) { + return $hit->valueObject; + }, + $searchResult->searchHits + ); + + $connection = ConnectionBuilder::connectionFromArraySlice( + $contentItems, + $args, + [ + 'sliceStart' => 0, + 'arrayLength' => $searchResult->totalCount, + ] + ); + $connection->sliceSize = count($contentItems); + + return $connection; + } +} diff --git a/GraphQL/Resolver/DomainContentResolver.php b/GraphQL/Resolver/DomainContentResolver.php index dd16b14..de37b68 100644 --- a/GraphQL/Resolver/DomainContentResolver.php +++ b/GraphQL/Resolver/DomainContentResolver.php @@ -11,14 +11,16 @@ use eZ\Publish\Core\FieldType; use eZ\Publish\API\Repository\ContentService; use eZ\Publish\API\Repository\ContentTypeService; +use eZ\Publish\API\Repository\Exceptions\NotFoundException; use eZ\Publish\API\Repository\SearchService; -use eZ\Publish\API\Repository\Values\Content\Content; use eZ\Publish\API\Repository\Values\Content\ContentInfo; use eZ\Publish\API\Repository\Values\Content\Query; use eZ\Publish\API\Repository\Values\Content\Search\SearchHit; use eZ\Publish\API\Repository\Values\ContentType\ContentType; +use eZ\Publish\SPI\Persistence\Content\Type\Handler as ContentTypeHandler; use GraphQL\Error\UserError; use Overblog\GraphQLBundle\Definition\Argument; +use Overblog\GraphQLBundle\Relay\Connection\Output\ConnectionBuilder; use Overblog\GraphQLBundle\Resolver\TypeResolver; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; @@ -54,34 +56,84 @@ class DomainContentResolver */ private $locationService; + /** + * @var \eZ\Publish\SPI\Persistence\Content\Type\Handler + */ + protected $contentTypeHandler; + public function __construct( ContentService $contentService, SearchService $searchService, ContentTypeService $contentTypeService, LocationService $locationService, TypeResolver $typeResolver, - SearchQueryMapper $queryMapper) - { + SearchQueryMapper $queryMapper, + ContentTypeHandler $contentTypeHandler + ) { $this->contentService = $contentService; $this->searchService = $searchService; $this->contentTypeService = $contentTypeService; $this->typeResolver = $typeResolver; $this->queryMapper = $queryMapper; $this->locationService = $locationService; + $this->contentTypeHandler = $contentTypeHandler; } - public function resolveDomainContentItems($contentTypeIdentifier, $query = null) + public function resolveDomainContentItems($contentTypeIdentifier, $args = null) { - return array_map( - function (Content $content) { - return $content->contentInfo; + $contentCount = $this->countContentOfType($contentTypeIdentifier); + + $beforeOffset = ConnectionBuilder::getOffsetWithDefault($args['before'], $contentCount); + $afterOffset = ConnectionBuilder::getOffsetWithDefault($args['after'], -1); + // echo "offset: $beforeOffset / $afterOffset\n"; + + $startOffset = max($afterOffset, -1) + 1; + $endOffset = min($beforeOffset, $contentCount); + + if (is_numeric($args['first'])) { + $endOffset = min($endOffset, $startOffset + $args['first']); + } + if (is_numeric($args['last'])) { + $startOffset = max($startOffset, $endOffset - $args['last']); + } + + $query = $args['query'] ?: []; + $query['ContentTypeIdentifier'] = $contentTypeIdentifier; + $query['offset'] = $limit = $offset = max($startOffset, 0); + $query['limit'] = $endOffset - $startOffset; + $query['sortBy'] = $args['sortBy']; + + $query = $this->queryMapper->mapInputToQuery($query); + $searchResults = $this->searchService->findContentInfo($query); + $items = array_map( + function (SearchHit $searchHit) { + return $searchHit->valueObject; }, - $this->findContentItemsByTypeIdentifier($contentTypeIdentifier, $query) + $searchResults->searchHits + ); + + $connection = ConnectionBuilder::connectionFromArraySlice( + $items, + $args, + [ + 'sliceStart' => $limit, + 'arrayLength' => $searchResults->totalCount, + ] ); + $connection->sliceSize = count($items); + + return $connection; } /** * Resolves a domain content item by id, and checks that it is of the requested type. + * @param int|string $contentId + * @param String $contentTypeIdentifier + * + * @return ContentInfo + * + * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException + * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException */ public function resolveDomainContentItem(Argument $args, $contentTypeIdentifier) { @@ -118,7 +170,7 @@ private function findContentItemsByTypeIdentifier($contentTypeIdentifier, Argume $args['query'] = $queryArg; $query = $this->queryMapper->mapInputToQuery($args['query']); - $searchResults = $this->searchService->findContent($query); + $searchResults = $this->searchService->findContentInfo($query); return array_map( function (SearchHit $searchHit) { @@ -199,4 +251,15 @@ private function makeDomainContentTypeName(ContentType $contentType) return $converter->denormalize($contentType->identifier) . 'Content'; } + + private function countContentOfType($contentTypeIdentifier): int + { + try { + return $this->contentTypeHandler->getContentCount( + $this->contentTypeService->loadContentTypeByIdentifier($contentTypeIdentifier)->id + ); + } catch (NotFoundException $e) { + return 0; + } + } } diff --git a/GraphQL/Resolver/ImageFieldResolver.php b/GraphQL/Resolver/ImageFieldResolver.php index 29e38b4..9af63a6 100644 --- a/GraphQL/Resolver/ImageFieldResolver.php +++ b/GraphQL/Resolver/ImageFieldResolver.php @@ -33,7 +33,34 @@ public function __construct(VariationHandler $variationHandler, ContentService $ $this->variations = $variations; } + public function resolveImageVariations(ImageFieldValue $fieldValue, $args) + { + list($content, $field) = $this->getImageField($fieldValue); + + $variations = []; + foreach ($args['identifier'] as $identifier) { + $versionInfo = $this->contentService->loadVersionInfo($content->contentInfo); + $variations[] = $this->variationHandler->getVariation($field, $versionInfo, $identifier); + } + + return $variations; + } + public function resolveImageVariation(ImageFieldValue $fieldValue, $args) + { + list($content, $field) = $this->getImageField($fieldValue); + $versionInfo = $this->contentService->loadVersionInfo($content->contentInfo); + + return $this->variationHandler->getVariation($field, $versionInfo, $args['identifier']); + } + + /** + * @param ImageFieldValue $fieldValue + * @return array + * @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException + * @throws \eZ\Publish\API\Repository\Exceptions\UnauthorizedException + */ + protected function getImageField(ImageFieldValue $fieldValue): array { $idArray = explode('-', $fieldValue->imageId); if (count($idArray) != 3) { @@ -61,15 +88,6 @@ public function resolveImageVariation(ImageFieldValue $fieldValue, $args) throw new UserError("Image file {$field->value->id} doesn't exist"); } - $variations = []; - foreach ($args['identifier'] as $identifier) { - $variations[] = $this->variationHandler->getVariation( - $field, - $this->contentService->loadVersionInfo($content->contentInfo), - $identifier - ); - } - - return $variations; + return array($content, $field); } } diff --git a/README.md b/README.md index 1e332e4..7ca3421 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ php bin/console bd:platform-graphql:generate-domain-schema php bin/console cache:clear ``` -It will generate a lot of yaml files in `src/AppBundle/Resources/config/graphql`, based on your content types. +It will generate a lot of yaml files in `app/config/graphql`, based on your content types. ### GraphiQL The graphical graphQL client, GraphiQL, must be installed separately if you want to use it. diff --git a/Resources/config/domain_content.yml b/Resources/config/domain_content.yml index f091c8d..2f071e8 100644 --- a/Resources/config/domain_content.yml +++ b/Resources/config/domain_content.yml @@ -48,3 +48,5 @@ services: BD\EzPlatformGraphQLBundle\DomainContent\FieldValueBuilder\SelectionFieldValueBuilder: tags: - {name: ezplatform_graphql.field_value_builder, type: 'ezselection'} + + BD\EzPlatformGraphQLBundle\Schema\ImagesVariationsBuilder: ~ \ No newline at end of file diff --git a/Resources/config/graphql/Content.types.yml b/Resources/config/graphql/Content.types.yml index 5ef72f2..322c3bf 100644 --- a/Resources/config/graphql/Content.types.yml +++ b/Resources/config/graphql/Content.types.yml @@ -1,12 +1,13 @@ Content: type: object config: + interfaces: [Node] description: "An eZ Platform repository ContentInfo." fields: id: - type: "Int!" + type: "ID!" + builder: Relay::GlobalId description: "The Content item's unique ID." - resolve: contentTypeId: type: "Int!" description: "The Content Type ID of the Content item." @@ -97,6 +98,7 @@ Content: type: "[ContentRelation]" description: "Relations to this Content" resolve: "@=resolver('ContentReverseRelations', [value])" + ContentRelation: type: "object" config: diff --git a/Resources/config/graphql/DomainContent.types.yml b/Resources/config/graphql/DomainContent.types.yml index 1c56f8f..0ef7988 100644 --- a/Resources/config/graphql/DomainContent.types.yml +++ b/Resources/config/graphql/DomainContent.types.yml @@ -30,6 +30,8 @@ AbstractDomainContent: id: type: "ID!" builder: Relay::GlobalId + builderConfig: + typeName: DomainContent description: "The Content item's unique ID." _type: description: "The item's Content type" @@ -80,4 +82,15 @@ BaseDomainContentType: fields: _info: type: ContentType - resolve: "@=value" \ No newline at end of file + resolve: "@=value" + +DomainContentByIdentifierConnection: + type: relay-connection + config: + connectionFields: + sliceSize: + type: Int! + orderBy: + # @todo make an enum or object + # @todo generate + type: String \ No newline at end of file diff --git a/Resources/config/graphql/Field.types.yml b/Resources/config/graphql/Field.types.yml index 64412b9..475ee3f 100644 --- a/Resources/config/graphql/Field.types.yml +++ b/Resources/config/graphql/Field.types.yml @@ -101,9 +101,16 @@ ImageFieldValue: type: "[ImageVariation]" args: identifier: - type: "[String]" + type: "[ImageVariationIdentifier]!" description: "One or more variation identifiers." resolve: "@=resolver('ImageVariation', [value.value, args])" + variation: + type: ImageVariation + args: + identifier: + type: ImageVariationIdentifier! + description: "A variation identifier." + resolve: "@=resolver('ImageVariation', [value.value, args])" html: type: "String" args: diff --git a/Resources/config/graphql/Platform.types.yml b/Resources/config/graphql/Platform.types.yml index 04c13da..9625750 100644 --- a/Resources/config/graphql/Platform.types.yml +++ b/Resources/config/graphql/Platform.types.yml @@ -5,4 +5,9 @@ Platform: _repository: type: Repository resolve: { } - description: "eZ Platform repository API" \ No newline at end of file + description: "eZ Platform repository API" + node: + builder: Relay::Node + builderConfig: + nodeInterfaceType: Node + idFetcher: '@=resolver("node", [value])' diff --git a/Resources/config/graphql/Repository.types.yml b/Resources/config/graphql/Repository.types.yml index 928899d..036efb6 100644 --- a/Resources/config/graphql/Repository.types.yml +++ b/Resources/config/graphql/Repository.types.yml @@ -15,7 +15,7 @@ Repository: args: id: description: "Resolves using the unique Content id." - type: "Int" + type: "ID!" remoteId: description: "Resolves using the unique Content remote id." type: "String" @@ -68,10 +68,10 @@ Repository: groupIdentifier: type: "String" resolve: "@=resolver('ContentTypesFromGroup', [args])" - searchContent: type: "[Content]" args: query: type: "ContentSearchQuery!" resolve: "@=resolver('SearchContent', [args])" + diff --git a/Resources/config/graphql/Search.types.yml b/Resources/config/graphql/Search.types.yml index 43386f7..a5e9ffc 100644 --- a/Resources/config/graphql/Search.types.yml +++ b/Resources/config/graphql/Search.types.yml @@ -23,6 +23,22 @@ ContentSearchQuery: Field: type: "FieldCriterionInput" description: "Field filter" + SortBy: + type: "SortByOptions" + +SortByOptions: + type: enum + config: + values: + ContentId: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\ContentId' + ContentName: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\ContentName' + DateModified: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\DateModified' + DatePublished: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\DatePublished' + # field: not implemented yet + Location: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\Location' + # MapLocationDistance: not implemented yet + SectionIdentifier: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\SectionIdentifier' + SectionName: '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\SectionName' FieldCriterionInput: type: "input-object" diff --git a/Resources/config/graphql/relay/Node.types.yml b/Resources/config/graphql/relay/Node.types.yml new file mode 100644 index 0000000..453a52e --- /dev/null +++ b/Resources/config/graphql/relay/Node.types.yml @@ -0,0 +1,7 @@ +# interface Node { +# id: ID! +# } +Node: + type: relay-node + config: + resolveType: '@=resolver("node_type", [value])' diff --git a/Resources/config/resolvers.yml b/Resources/config/resolvers.yml index 2f9a11e..a6d272a 100644 --- a/Resources/config/resolvers.yml +++ b/Resources/config/resolvers.yml @@ -49,6 +49,7 @@ services: $contentService: '@ezpublish.siteaccessaware.service.content' $contentTypeService: '@ezpublish.siteaccessaware.service.content_type' $locationService: '@ezpublish.siteaccessaware.service.location' + $contentTypeHandler: '@ezpublish.spi.persistence.content_type_handler' BD\EzPlatformGraphQLBundle\GraphQL\Resolver\UserResolver: tags: @@ -110,6 +111,7 @@ services: $contentService: "@ezpublish.siteaccessaware.service.content" $variations: "$image_variations$" tags: + - { name: overblog_graphql.resolver, alias: "ImageVariations", method: "resolveImageVariations" } - { name: overblog_graphql.resolver, alias: "ImageVariation", method: "resolveImageVariation" } BD\EzPlatformGraphQLBundle\GraphQL\Resolver\DateResolver: @@ -123,3 +125,12 @@ services: BD\EzPlatformGraphQLBundle\GraphQL\Resolver\SelectionFieldResolver: tags: - { name: overblog_graphql.resolver, alias: "SelectionFieldValue", method: "resolveSelectionFieldValue"} + + BD\EzPlatformGraphQLBundle\GraphQL\Relay\NodeResolver: + tags: + - { name: overblog_graphql.resolver, alias: "node", method: "resolveNode" } + - { name: overblog_graphql.resolver, alias: "node_type", method: "resolveType" } + + BD\EzPlatformGraphQLBundle\GraphQL\Relay\SearchResolver: + tags: + - {name: overblog_graphql.resolver, alias: "SearchContentConnection", method: "searchContent"} diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 6a8fe41..bd5fd70 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -1,20 +1,25 @@ services: - BD\EzPlatformGraphQLBundle\Command\GeneratePlatformDomainTypesCommand: + _defaults: autoconfigure: true autowire: true + public: false + + BD\EzPlatformGraphQLBundle\Command\GeneratePlatformSchemaCommand: tags: - { name: console.command } - BD\EzPlatformGraphQLBundle\Command\GeneratePlatformSchemaCommand: - autoconfigure: true - autowire: true + BD\EzPlatformGraphQLBundle\Command\GeneratePlatformDomainTypesCommand: tags: - { name: console.command } + BD\EzPlatformGraphQLBundle\GraphQL\Resolver\LocationResolver: + arguments: + - "@ezpublish.api.repository" + BD\EzPlatformGraphQLBundle\GraphQL\TypeDefinition\ContentTypeMapper: ~ - bd_ezplatform_graphql.graph.mutation.section: - class: BD\EzPlatformGraphQLBundle\GraphQL\Mutation\SectionMutation + + BD\EzPlatformGraphQLBundle\GraphQL\Mutation\SectionMutation: arguments: - "@ezpublish.api.service.section" tags: diff --git a/Schema/ImagesVariationsBuilder.php b/Schema/ImagesVariationsBuilder.php new file mode 100644 index 0000000..146d90e --- /dev/null +++ b/Schema/ImagesVariationsBuilder.php @@ -0,0 +1,36 @@ +configResolver = $configResolver; + } + + public function build(array &$schema) + { + $schema['ImageVariationIdentifier'] = [ + 'type' => 'enum', + 'config' => [ + 'values' => [] + ] + ]; + + $values =& $schema['ImageVariationIdentifier']['config']['values']; + + foreach (array_keys($this->configResolver->getParameter('image_variations')) as $variationIdentifier) { + $values[$variationIdentifier] = []; + } + } +} \ No newline at end of file diff --git a/TODO.md b/TODO.md index 5031e48..0e4cd93 100644 --- a/TODO.md +++ b/TODO.md @@ -12,3 +12,5 @@ Dedicated ContentType objects would be instances of their ContentType, and provide direct access to their fields. `ArticleContent` would have `title`, `intro`, `body` and `image` fields. +- Fix lists by adding the `edges {cursor node}` structure specified by Relay + https://facebook.github.io/relay/graphql/connections.htm diff --git a/doc/domain_schema.md b/doc/domain_schema.md index c4c177a..c81f147 100644 --- a/doc/domain_schema.md +++ b/doc/domain_schema.md @@ -20,7 +20,7 @@ Queries look like this: body { html } image { name - variations(alias: large) { uri } + variations(identifier: large) { uri } } } folders { @@ -34,9 +34,9 @@ Queries look like this: Run `php bin/console bd:platform-graphql:generate-domain-schema` from the root of your eZ Platform installation. It will go over your repository, and generate the matching -types in `src/AppBundle/Resources/graphql/`. +types in `app/config/graphql/`. -Open `/graphiql/domain`. The content type groups, content types and their fields +Open `/graphiql`. The content type groups, content types and their fields will be exposed as the schema. ## Customizing the schema