Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class RelationFieldDefinitionMapperSpec extends ObjectBehavior

function let(NameHelper $nameHelper, ContentTypeService $contentTypeService, FieldDefinitionMapper $innerMapper)
{
$this->beConstructedWith($innerMapper, $nameHelper, $contentTypeService);
$this->beConstructedWith($innerMapper, $nameHelper, $contentTypeService, true);

$articleContentType = new ContentType(['identifier' => 'article']);
$folderContentType = new ContentType(['identifier' => 'folder']);
Expand Down Expand Up @@ -56,19 +56,19 @@ function it_maps_single_selection_with_a_unique_type_limitations_to_a_single_ite
function it_maps_multi_selection_without_type_limitations_to_an_array_of_generic_content()
{
$fieldDefinition = $this->createFieldDefinition(self::DEF_LIMIT_MULTI, []);
$this->mapToFieldValueType($fieldDefinition)->shouldReturn('[Item]');
$this->mapToFieldValueType($fieldDefinition)->shouldReturn('RelationsConnection');
}

function it_maps_multi_selection_with_multiple_type_limitations_to_an_array_of_generic_content()
{
$fieldDefinition = $this->createFieldDefinition(self::DEF_LIMIT_NONE, ['article', 'blog_post']);
$this->mapToFieldValueType($fieldDefinition)->shouldReturn('[Item]');
$this->mapToFieldValueType($fieldDefinition)->shouldReturn('RelationsConnection');
}

function it_maps_multi_selection_with_a_unique_type_limitations_to_an_array_of_that_type()
{
$fieldDefinition = $this->createFieldDefinition(self::DEF_LIMIT_MULTI, ['article']);
$this->mapToFieldValueType($fieldDefinition)->shouldReturn('[ArticleItem]');
$this->mapToFieldValueType($fieldDefinition)->shouldReturn('RelationsConnection');
}

function it_delegates_the_field_definition_type_to_the_inner_mapper(FieldDefinitionMapper $innerMapper)
Expand All @@ -81,13 +81,13 @@ function it_delegates_the_field_definition_type_to_the_inner_mapper(FieldDefinit
function it_maps_multi_selection_to_resolve_multiple()
{
$fieldDefinition = $this->createFieldDefinition(self::DEF_LIMIT_MULTI);
$this->mapToFieldValueResolver($fieldDefinition)->shouldReturn('@=resolver("RelationFieldValue", [field, true])');
$this->mapToFieldValueResolver($fieldDefinition)->shouldReturn('@=resolver("RelationFieldValue", [field, true, args])');
}

function it_maps_single_selection_to_resolve_single()
{
$fieldDefinition = $this->createFieldDefinition(self::DEF_LIMIT_SINGLE);
$this->mapToFieldValueResolver($fieldDefinition)->shouldReturn('@=resolver("RelationFieldValue", [field, false])');
$this->mapToFieldValueResolver($fieldDefinition)->shouldReturn('@=resolver("RelationFieldValue", [field, false, args])');
}

private function createFieldDefinition($selectionLimit = 0, $selectionContentTypes = [])
Expand Down
1 change: 1 addition & 0 deletions src/bundle/Resources/config/default_settings.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
parameters:
ibexa.graphql.schema.should.extend.ezurl: false
ibexa.graphql.schema.ibexa_object_relation_list.enable_pagination: false
ibexa.graphql.schema.content.field_name.override:
id: id_
ibexa.graphql.schema.content.mapping.field_definition_type:
Expand Down
10 changes: 10 additions & 0 deletions src/bundle/Resources/config/graphql/Field.types.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,13 @@ UrlFieldValue:
type: String
description: "The link's name or description"
resolve: "@=value.text"

RelationsConnection:
type: relay-connection
config:
nodeType: Item
connectionFields:
sliceSize:
type: Int!
orderBy:
type: String
2 changes: 2 additions & 0 deletions src/bundle/Resources/config/services/resolvers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ services:
- { name: overblog_graphql.resolver, alias: "ItemsOfTypeAsConnection", method: "resolveItemsOfTypeAsConnection" }

Ibexa\GraphQL\Resolver\RelationFieldResolver:
arguments:
$enablePagination: '%ibexa.graphql.schema.ezobjectrelationlist.enable_pagination%'
tags:
- { name: overblog_graphql.resolver, alias: "RelationFieldValue", method: "resolveRelationFieldValue" }

Expand Down
1 change: 1 addition & 0 deletions src/bundle/Resources/config/services/schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ services:
arguments:
$contentTypeService: '@ibexa.siteaccessaware.service.content_type'
$innerMapper: '@Ibexa\GraphQL\Schema\Domain\Content\Mapper\FieldDefinition\RelationFieldDefinitionMapper.inner'
$enablePagination: '%ibexa.graphql.schema.ibexa_object_relation_list.enable_pagination%'

Ibexa\GraphQL\Schema\Domain\Content\Mapper\FieldDefinition\SelectionFieldDefinitionMapper:
decorates: Ibexa\Contracts\GraphQL\Schema\Domain\Content\Mapper\FieldDefinition\FieldDefinitionMapper
Expand Down
82 changes: 63 additions & 19 deletions src/lib/Resolver/RelationFieldResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,91 @@
namespace Ibexa\GraphQL\Resolver;

use GraphQL\Error\UserError;
use Ibexa\Contracts\Core\Repository\Values\Content\Content;
use Ibexa\Contracts\Core\Repository\Values\Content\Query;
use Ibexa\Core\FieldType;
use Ibexa\GraphQL\DataLoader\ContentLoader;
use Ibexa\GraphQL\ItemFactory;
use Ibexa\GraphQL\Relay\PageAwareConnection;
use Ibexa\GraphQL\Value\Field;
use Overblog\GraphQLBundle\Definition\Argument;
use Overblog\GraphQLBundle\Relay\Connection\Paginator;

final class RelationFieldResolver
{
/** @var \Ibexa\GraphQL\DataLoader\ContentLoader */
private $contentLoader;
public const DEFAULT_LIMIT = 25;

/** @var \Ibexa\GraphQL\ItemFactory */
private $itemFactory;
private ContentLoader $contentLoader;

public function __construct(ContentLoader $contentLoader, ItemFactory $relatedContentItemFactory)
{
private ItemFactory $itemFactory;

private bool $enablePagination;

public function __construct(
ContentLoader $contentLoader,
ItemFactory $relatedContentItemFactory,
bool $enablePagination
) {
$this->contentLoader = $contentLoader;
$this->itemFactory = $relatedContentItemFactory;
$this->enablePagination = $enablePagination;
}

public function resolveRelationFieldValue(Field $field, $multiple = false)
public function resolveRelationFieldValue(Field $field, $multiple = false, ?Argument $args = null)
{
$destinationContentIds = $this->getContentIds($field);

if (empty($destinationContentIds) || array_key_exists(0, $destinationContentIds) && null === $destinationContentIds[0]) {
return $multiple ? [] : null;
}

$contentItems = $this->contentLoader->find(new Query(
$query = new Query(
['filter' => new Query\Criterion\ContentId($destinationContentIds)]
));
);

if ($multiple) {
return array_map(
function ($contentId) use ($contentItems) {
return $this->itemFactory->fromContent(
$contentItems[array_search($contentId, array_column($contentItems, 'id'))]
);
},
$destinationContentIds
if (!$this->enablePagination || $args === null) {
$contentItems = $this->contentLoader->find($query);

return array_map(
function (int $contentId) use ($contentItems) {
return $this->itemFactory->fromContent(
$contentItems[array_search($contentId, array_column($contentItems, 'id'), true)]
);
},
$destinationContentIds
);
}

$paginator = new Paginator(function ($offset, $limit) use ($query) {
$query->offset = $offset;
$query->limit = $limit ?? self::DEFAULT_LIMIT;
$contentItems = $this->contentLoader->find($query);

return array_map(
function (Content $content) {
return $this->itemFactory->fromContent(
$content
);
},
$contentItems
);
});

return PageAwareConnection::fromConnection(
$paginator->auto(
$args,
function () use ($query) {
return $this->contentLoader->count($query);
}
),
$args
);
}

$query->limit = 1;
$contentItems = $this->contentLoader->find($query);

return $contentItems[0] ? $this->itemFactory->fromContent($contentItems[0]) : null;
}

Expand All @@ -62,11 +104,13 @@ private function getContentIds(Field $field): array
{
if ($field->value instanceof FieldType\RelationList\Value) {
return $field->value->destinationContentIds;
} elseif ($field->value instanceof FieldType\Relation\Value) {
}

if ($field->value instanceof FieldType\Relation\Value) {
return [$field->value->destinationContentId];
} else {
throw new UserError('\$field does not contain a RelationList or Relation Field value');
}

throw new UserError('\$field does not contain a RelationList or Relation Field value');
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,22 @@

class RelationFieldDefinitionMapper extends DecoratingFieldDefinitionMapper implements FieldDefinitionMapper
{
/**
* @var \Ibexa\GraphQL\Schema\Domain\Content\NameHelper
*/
private $nameHelper;
private NameHelper $nameHelper;

/**
* @var \Ibexa\Contracts\Core\Repository\ContentTypeService
*/
private $contentTypeService;
private ContentTypeService $contentTypeService;

private bool $enablePagination;

public function __construct(
FieldDefinitionMapper $innerMapper,
NameHelper $nameHelper,
ContentTypeService $contentTypeService
ContentTypeService $contentTypeService,
bool $enablePagination
) {
parent::__construct($innerMapper);
$this->nameHelper = $nameHelper;
$this->contentTypeService = $contentTypeService;
$this->enablePagination = $enablePagination;
}

public function mapToFieldValueType(FieldDefinition $fieldDefinition): ?string
Expand All @@ -54,7 +52,18 @@ public function mapToFieldValueType(FieldDefinition $fieldDefinition): ?string
}

if ($this->isMultiple($fieldDefinition)) {
$type = "[$type]";
if ($this->enablePagination) {
$type = 'RelationsConnection';
} else {
@trigger_error(
'Disable pagination for ezobjectrelationlist has been deprecated since version 4.6 ' .
'and will be removed in version 5.0. To start receiving `RelationsConnection` instead of the deprecated ' .
'`[' . $type . ']`, set the parameter `ibexa.graphql.schema.ibexa_object_relation_list.enable_pagination` to `true`.',
E_USER_DEPRECATED
);

$type = "[$type]";
}
}

return $type;
Expand All @@ -68,14 +77,27 @@ public function mapToFieldValueResolver(FieldDefinition $fieldDefinition): ?stri

$isMultiple = $this->isMultiple($fieldDefinition) ? 'true' : 'false';

return sprintf('@=resolver("RelationFieldValue", [field, %s])', $isMultiple);
return sprintf('@=resolver("RelationFieldValue", [field, %s, args])', $isMultiple);
}

protected function canMap(FieldDefinition $fieldDefinition)
{
return in_array($fieldDefinition->fieldTypeIdentifier, ['ezobjectrelation', 'ezobjectrelationlist']);
}

public function mapToFieldValueArgsBuilder(FieldDefinition $fieldDefinition): ?string
{
if (!$this->canMap($fieldDefinition)) {
return parent::mapToFieldValueArgsBuilder($fieldDefinition);
}

if ($this->isMultiple($fieldDefinition) && $this->enablePagination) {
return 'Relay::Connection';
}

return parent::mapToFieldValueArgsBuilder($fieldDefinition);
}

/**
* Not implemented since we don't use it (canMap is overridden).
*/
Expand Down
Loading