Skip to content

Commit

Permalink
Implemented pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
Bertrand Dunogier committed Feb 5, 2020
1 parent 642b225 commit 23f8820
Show file tree
Hide file tree
Showing 16 changed files with 405 additions and 42 deletions.
23 changes: 20 additions & 3 deletions doc/howto/customize_rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ In this how-to, you will learn how to customize all the rendering phases.

## Rendering process
Content query fields are rendered using `ez_render_field()`.
The query is executed, the items iterated on, and each is be displayed using the `line` content view.
The query is executed, the items iterated on, and each is rendered using the `line` content view.

That template renders the content, the view controller, with a custom view type (`content_query_view`). A custom view
builder executes execute the query, and assigns the results to the view as `items`. The default template for that view (`query_field_view.html.twig`) iterates on each item resulting from the query, and renders each with the `line` view.

The field renderer for a query field supports the following parameters:
- `bool enablePagination`: force pagination enabled, even if it is disabled for that field definition
- `bool disablePagination`: force pagination disabled, even if it is disabled for that field definition
- `int itemsPerPage`: sets how many items are displayed per page with pagination. Required if `enablePagination` is
used and pagination is disabled in the field definition

### Summary
1. Your template: `ez_render_field(content, 'queryfield')`
2. Field view template: `fieldtype_ui.html.twig`:
Expand Down Expand Up @@ -41,16 +47,27 @@ As with any content view, a custom controller can also be defined to further cus
Use the `items` iterable to loop over the field's content items:
```
<div class="my-list">
{% for item in items %}
{{ render(controller("ez_content:viewAction", {
"contentId": item.id,
"content": item,
"viewType": itemViewType
})) }}
{% endfor %}
</div>

{% if isPaginationEnabled %}
{{ pagerfanta( items, 'ez', {'routeName': location, 'pageParameter': pageParameter } ) }}
{% endif %}
```
The usual [content view templates variables](https://doc.ezplatform.com/en/latest/api/field_type_form_and_template/#template-variables) are also available, including `content`, the item that contains the query field.
In addition to the [usual content view templates variables](https://doc.ezplatform.com/en/latest/api/field_type_form_and_template/#template-variables), the following variables are available:
- `Content content`: the item that contains the query field.
- `bool isPaginationEnabled`: indicates if pagination is enabled. When it is, `items` is a `PagerFanta` instance.
- `string pageParameter`: when pagination is enabled, contains the page parameter to use as the pager fanta
`pageParameter` argument (important, as it makes every pager unique, required if there are several query
fields in the same item)
## Customizing the line view template
The line view template, used to render each result, can be customized by creating `line` view configuration rules.
Expand All @@ -69,7 +86,7 @@ ezplatform:
template: "path/to/template.html.twig"
```

The variables are the same as any view template ([documentation]((https://doc.ezplatform.com/en/latest/api/field_type_form_and_template/#template-variables))).
The variables are the same than other view template ([documentation]((https://doc.ezplatform.com/en/latest/api/field_type_form_and_template/#template-variables))).

## Advanced

Expand Down
24 changes: 19 additions & 5 deletions spec/GraphQL/ContentQueryFieldDefinitionMapperSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,24 +79,38 @@ function it_delegates_field_definition_type_to_the_parent_mapper_for_a_non_query
->shouldBe('FieldValue');
}

function it_delegates_the_value_resolver_to_the_parent_mapper(FieldDefinitionMapper $innerMapper)
function it_maps_the_field_value_when_pagination_is_disabled(FieldDefinitionMapper $innerMapper)
{
$fieldDefinition = $this->fieldDefinition();
$innerMapper->mapToFieldValueResolver($fieldDefinition)->willReturn('resolver');
$innerMapper->mapToFieldValueResolver($fieldDefinition)->shouldNotBeCalled();
$this
->mapToFieldValueResolver($fieldDefinition)
->shouldBe('resolver');
->shouldBe('@=resolver("QueryFieldValue", [field, content])');
}

function it_maps_the_field_value_when_pagination_is_enabled(FieldDefinitionMapper $innerMapper)
{
$fieldDefinition = $this->fieldDefinition(true);
$innerMapper->mapToFieldValueResolver($fieldDefinition)->shouldNotBeCalled();
$this
->mapToFieldValueResolver($fieldDefinition)
->shouldBe('@=resolver("QueryFieldValueConnection", [args, field, content])');
}

/**
* @param bool $enablePagination
*
* @return FieldDefinition
*/
private function fieldDefinition(): FieldDefinition
private function fieldDefinition(bool $enablePagination = false): FieldDefinition
{
return new FieldDefinition([
'identifier' => self::FIELD_IDENTIFIER,
'fieldTypeIdentifier' => self::FIELD_TYPE_IDENTIFIER,
'fieldSettings' => ['ReturnedType' => self::RETURNED_CONTENT_TYPE_IDENTIFIER]
'fieldSettings' => [
'ReturnedType' => self::RETURNED_CONTENT_TYPE_IDENTIFIER,
'EnablePagination' => $enablePagination
]
]);
}

Expand Down
21 changes: 21 additions & 0 deletions src/API/QueryFieldPaginationService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

/**
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
namespace EzSystems\EzPlatformQueryFieldType\API;

use eZ\Publish\API\Repository\Values\Content\Content;

/**
* Pagination related methods for v1.0.
*
* @deprecated since 1.0, will be part of the regular QueryFieldService interface in 2.0.
*/
interface QueryFieldPaginationService
{
public function getPaginationConfiguration(Content $content, string $fieldDefinitionIdentifier): int;

public function loadContentItemsSlice(Content $content, string $fieldDefinitionIdentifier, int $offset, int $limit): iterable;
}
78 changes: 60 additions & 18 deletions src/API/QueryFieldService.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@
use eZ\Publish\API\Repository\Values\Content\Content;
use eZ\Publish\API\Repository\Values\Content\Query;
use eZ\Publish\API\Repository\Values\Content\Search\SearchHit;
use eZ\Publish\API\Repository\Values\ContentType\FieldDefinition;
use eZ\Publish\Core\QueryType\QueryTypeRegistry;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;

/**
* Executes a query and returns the results.
*/
final class QueryFieldService implements QueryFieldServiceInterface
final class QueryFieldService implements QueryFieldServiceInterface, QueryFieldPaginationService
{
/** @var \eZ\Publish\Core\QueryType\QueryTypeRegistry */
private $queryTypeRegistry;
Expand Down Expand Up @@ -73,23 +74,48 @@ public function countContentItems(Content $content, string $fieldDefinitionIdent
return $this->searchService->findContent($query)->totalCount;
}

public function loadContentItemsSlice(Content $content, string $fieldDefinitionIdentifier, int $offset, int $limit): iterable
{
$query = $this->prepareQuery($content, $fieldDefinitionIdentifier);
$query->offset = $offset;
$query->limit = $limit;

return array_map(
function (SearchHit $searchHit) {
return $searchHit->valueObject;
},
$this->searchService->findContent($query)->searchHits
);
}

public function getPaginationConfiguration(Content $content, string $fieldDefinitionIdentifier): int
{
$fieldDefinition = $this->loadFieldDefinition($content, $fieldDefinitionIdentifier);

if ($fieldDefinition->fieldSettings['EnablePagination'] === false) {
return false;
}

return $fieldDefinition->fieldSettings['ItemsPerPage'];
}

/**
* @param array $parameters parameters that may include expressions to be resolved
* @param \eZ\Publish\API\Repository\Values\Content\Content $content
* @param array $expressions parameters that may include expressions to be resolved
* @param array $variables
*
* @return array
*/
private function resolveParameters(array $parameters, array $variables): array
private function resolveParameters(array $expressions, array $variables): array
{
foreach ($parameters as $key => $expression) {
foreach ($expressions as $key => $expression) {
if (is_array($expression)) {
$parameters[$key] = $this->resolveParameters($expression, $variables);
$expressions[$key] = $this->resolveParameters($expression, $variables);
} else {
$parameters[$key] = $this->resolveExpression($expression, $variables);
$expressions[$key] = $this->resolveExpression($expression, $variables);
}
}

return $parameters;
return $expressions;
}

private function resolveExpression(string $expression, array $variables)
Expand All @@ -110,24 +136,40 @@ private function resolveExpression(string $expression, array $variables)
* @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentException
* @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
*/
private function prepareQuery(Content $content, string $fieldDefinitionIdentifier): Query
private function prepareQuery(Content $content, string $fieldDefinitionIdentifier, array $extraParameters = []): Query
{
$fieldDefinition = $this
->contentTypeService->loadContentType($content->contentInfo->contentTypeId)
->getFieldDefinition($fieldDefinitionIdentifier);
$fieldDefinition = $this->loadFieldDefinition($content, $fieldDefinitionIdentifier);

$location = $this->locationService->loadLocation($content->contentInfo->mainLocationId);
$queryType = $this->queryTypeRegistry->getQueryType($fieldDefinition->fieldSettings['QueryType']);
$parameters = $this->resolveParameters(
$fieldDefinition->fieldSettings['Parameters'],
[
'content' => $content,
'contentInfo' => $content->contentInfo,
'mainLocation' => $location,
'returnedType' => $fieldDefinition->fieldSettings['ReturnedType'],
]
array_merge(
$extraParameters,
[
'content' => $content,
'contentInfo' => $content->contentInfo,
'mainLocation' => $location,
'returnedType' => $fieldDefinition->fieldSettings['ReturnedType'],
]
)
);

return $queryType->getQuery($parameters);
}

/**
* @param \eZ\Publish\API\Repository\Values\Content\Content $content
* @param string $fieldDefinitionIdentifier
*
* @return \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition|null
*
* @throws \eZ\Publish\API\Repository\Exceptions\NotFoundException
*/
private function loadFieldDefinition(Content $content, string $fieldDefinitionIdentifier): FieldDefinition
{
return $fieldDefinition = $this
->contentTypeService->loadContentType($content->contentInfo->contentTypeId)
->getFieldDefinition($fieldDefinitionIdentifier);
}
}
21 changes: 18 additions & 3 deletions src/Controller/QueryFieldRestController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use eZ\Publish\API\Repository\Values\ContentType\ContentType;
use eZ\Publish\Core\REST\Server\Values\ContentList;
use eZ\Publish\Core\REST\Server\Values\RestContent;
use Symfony\Component\HttpFoundation\Request;

final class QueryFieldRestController
{
Expand Down Expand Up @@ -42,11 +43,19 @@ public function __construct(
$this->locationService = $locationService;
}

public function getResults($contentId, $versionNumber, $fieldDefinitionIdentifier): ContentList
public function getResults(Request $request, $contentId, $versionNumber, $fieldDefinitionIdentifier): ContentList
{
$offset = (int)$request->query->get('offset', 0);
$limit = (int)$request->query->get('limit', -1);

$content = $this->contentService->loadContent($contentId, null, $versionNumber);
if ($limit === -1 || !method_exists($this->queryFieldService, 'loadContentItemsSlice')) {
$items = $this->queryFieldService->loadContentItems($content, $fieldDefinitionIdentifier);
} else {
$items = $this->queryFieldService->loadContentItemsSlice($content, $fieldDefinitionIdentifier, $offset, $limit);
}

return new ContentList(
$list = new ContentList(
array_map(
function (Content $content) {
return new RestContent(
Expand All @@ -57,9 +66,15 @@ function (Content $content) {
$this->contentService->loadRelations($content->getVersionInfo())
);
},
$this->queryFieldService->loadContentItems($content, $fieldDefinitionIdentifier)
$items
)
);

if (property_exists($list, 'totalCount')) {
$list->totalCount = $this->queryFieldService->countContentItems($content, $fieldDefinitionIdentifier);
}

return $list;
}

private function getContentType(ContentInfo $contentInfo): ContentType
Expand Down
46 changes: 43 additions & 3 deletions src/GraphQL/ContentQueryFieldDefinitionMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
use eZ\Publish\API\Repository\ContentTypeService;
use eZ\Publish\API\Repository\Values\ContentType\FieldDefinition;
use EzSystems\EzPlatformGraphQL\Schema\Domain\Content\Mapper\FieldDefinition\DecoratingFieldDefinitionMapper;
use EzSystems\EzPlatformGraphQL\Schema\Domain\Content\Mapper\FieldDefinition\FieldDefinitionArgsBuilderMapper;
use EzSystems\EzPlatformGraphQL\Schema\Domain\Content\Mapper\FieldDefinition\FieldDefinitionMapper;
use EzSystems\EzPlatformGraphQL\Schema\Domain\Content\NameHelper;

final class ContentQueryFieldDefinitionMapper extends DecoratingFieldDefinitionMapper implements FieldDefinitionMapper
final class ContentQueryFieldDefinitionMapper extends DecoratingFieldDefinitionMapper implements FieldDefinitionMapper, FieldDefinitionArgsBuilderMapper
{
/** @var NameHelper */
private $nameHelper;
Expand All @@ -38,7 +39,26 @@ public function mapToFieldValueType(FieldDefinition $fieldDefinition): ?string

$fieldSettings = $fieldDefinition->getFieldSettings();

return '[' . $this->getDomainTypeName($fieldSettings['ReturnedType']) . ']';
if ($fieldSettings['EnablePagination']) {
return $this->nameValueConnectionType($fieldSettings['ReturnedType']);
} else {
return '[' . $this->nameValueType($fieldSettings['ReturnedType']) . ']';
}
}

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

$fieldSettings = $fieldDefinition->getFieldSettings();

if ($fieldSettings['EnablePagination']) {
return '@=resolver("QueryFieldValueConnection", [args, field, content])';
} else {
return '@=resolver("QueryFieldValue", [field, content])';
}
}

public function mapToFieldDefinitionType(FieldDefinition $fieldDefinition): ?string
Expand All @@ -50,15 +70,35 @@ public function mapToFieldDefinitionType(FieldDefinition $fieldDefinition): ?str
return 'ContentQueryFieldDefinition';
}

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

if ($fieldDefinition->fieldSettings['EnablePagination']) {
return 'Relay::Connection';
} else {
return null;
}
}

protected function getFieldTypeIdentifier(): string
{
return 'ezcontentquery';
}

private function getDomainTypeName($typeIdentifier)
private function nameValueType($typeIdentifier): string
{
return $this->nameHelper->domainContentName(
$this->contentTypeService->loadContentTypeByIdentifier($typeIdentifier)
);
}

private function nameValueConnectionType($typeIdentifier): string
{
return $this->nameHelper->domainContentConnection(
$this->contentTypeService->loadContentTypeByIdentifier($typeIdentifier)
);
}
}
Loading

0 comments on commit 23f8820

Please sign in to comment.