Skip to content

Commit

Permalink
EZP-31107: Implemented sorting specification
Browse files Browse the repository at this point in the history
  • Loading branch information
adamwojs committed Feb 27, 2020
1 parent 3247334 commit f636bc9
Show file tree
Hide file tree
Showing 36 changed files with 1,602 additions and 234 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,9 @@ private function handleApiLoading(ContainerBuilder $container, FileLoader $loade

// Build-in query types
$loader->load('query_types.yml');

// Sorting specification parser
$loader->load('sort_spec.yml');
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,10 @@ services:
eZ\Publish\Core\QueryType\BuildIn\SubtreeQueryType:
tags:
- { name: ezpublish.query_type, alias: 'eZ:Subtree' }

eZ\Publish\Core\QueryType\BuildIn\SortClausesFactory:
arguments:
$sortClauseArgsParser: '@eZ\Publish\Core\QueryType\BuildIn\SortSpec\SortClauseParserDispatcher'

eZ\Publish\Core\QueryType\BuildIn\SortClausesFactoryInterface:
alias: 'eZ\Publish\Core\QueryType\BuildIn\SortClausesFactory'
39 changes: 39 additions & 0 deletions eZ/Bundle/EzPublishCoreBundle/Resources/config/sort_spec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
services:
_defaults:
autowire: true
autoconfigure: true
public: false

eZ\Publish\Core\QueryType\BuildIn\SortSpec\SortClauseParserDispatcher:
arguments:
$parsers: !tagged_iterator ezplatform.query_type.sort_clause_parser

eZ\Publish\Core\QueryType\BuildIn\SortSpec\SortClauseParser\FieldSortClauseParser:
tags:
- { name: ezplatform.query_type.sort_clause_parser }

eZ\Publish\Core\QueryType\BuildIn\SortSpec\SortClauseParser\MapDistanceSortClauseParser:
tags:
- { name: ezplatform.query_type.sort_clause_parser }

eZ\Publish\Core\QueryType\BuildIn\SortSpec\SortClauseParser\RandomSortClauseParser:
tags:
- { name: ezplatform.query_type.sort_clause_parser }

eZ\Publish\Core\QueryType\BuildIn\SortSpec\SortClauseParser\DefaultSortClauseParser:
arguments:
$valueObjectClassMap:
content_id: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\ContentId
content_name: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\ContentName
date_modified: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\DateModified
date_published: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\DatePublished
section_identifier: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\SectionIdentifier
section_name: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\SectionName
location_depth: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Location\Depth
location_id: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Location\Id
location_is_main: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Location\IsMainLocation
location_path: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Location\Path
location_priority: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Location\Priority
location_visibility: \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Location\Visibility
tags:
- { name: ezplatform.query_type.sort_clause_parser }
65 changes: 24 additions & 41 deletions eZ/Publish/Core/QueryType/BuildIn/AbstractQueryType.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\LogicalAnd;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\Subtree;
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\Visibility;
use eZ\Publish\API\Repository\Values\Content\Query\SortClause;
use eZ\Publish\Core\MVC\ConfigResolverInterface;
use eZ\Publish\Core\QueryType\OptionsResolverBasedQueryType;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;

abstract class AbstractQueryType extends OptionsResolverBasedQueryType
{
private const SORT_CLAUSE_NAMESPACE = '\eZ\Publish\API\Repository\Values\Content\Query\SortClause\\';

public const DEFAULT_LIMIT = 25;

/** @var \eZ\Publish\API\Repository\Repository */
Expand All @@ -32,10 +31,17 @@ abstract class AbstractQueryType extends OptionsResolverBasedQueryType
/** @var \eZ\Publish\Core\MVC\ConfigResolverInterface */
protected $configResolver;

public function __construct(Repository $repository, ConfigResolverInterface $configResolver)
{
/** @var \eZ\Publish\Core\QueryType\BuildIn\SortClausesFactoryInterface */
private $sortClausesFactory;

public function __construct(
Repository $repository,
ConfigResolverInterface $configResolver,
SortClausesFactoryInterface $sortSpecParserFactory
) {
$this->repository = $repository;
$this->configResolver = $configResolver;
$this->sortClausesFactory = $sortSpecParserFactory;
}

protected function configureOptions(OptionsResolver $resolver): void
Expand All @@ -54,24 +60,22 @@ protected function configureOptions(OptionsResolver $resolver): void
},
'offset' => 0,
'limit' => self::DEFAULT_LIMIT,
'sort' => static function (OptionsResolver $resolver): void {
$resolver->setDefault('target', null);
$resolver->setAllowedTypes('target', ['null', 'string']);
'sort' => [],
]);

$resolver->setDefault('direction', 'asc');
$resolver->setAllowedValues('direction', ['asc', 'desc']);
$resolver->setNormalizer('direction', function (Options $options, $direction) {
if (strtolower($direction) === 'desc') {
return Query::SORT_DESC;
}
$resolver->setNormalizer('sort', function (Options $options, $value) {
if (is_string($value)) {
$value = $this->sortClausesFactory->createFromSpecification($value);
}

return Query::SORT_ASC;
});
if (!is_array($value)) {
$value = [$value];
}

$resolver->setDefault('data', null);
},
]);
return $value;
});

$resolver->setAllowedTypes('sort', ['string', 'array', SortClause::class]);
$resolver->setAllowedTypes('offset', 'int');
$resolver->setAllowedTypes('limit', 'int');
}
Expand All @@ -83,12 +87,8 @@ protected function doGetQuery(array $parameters): Query
$query = new Query();
$query->filter = $this->buildFilters($parameters);

if ($parameters['sort']['target'] !== null) {
$query->sortClauses = $this->buildSortClauses(
$parameters['sort']['target'],
$parameters['sort']['direction'],
$parameters['sort']['data'] ?? []
);
if ($parameters['sort'] !== null) {
$query->sortClauses = $parameters['sort'];
}

$query->limit = $parameters['limit'];
Expand Down Expand Up @@ -119,23 +119,6 @@ private function buildFilters(array $parameters): Criterion
return new LogicalAnd($criteria);
}

private function buildSortClauses(string $class, ?string $direction, array $args): array
{
if (substr($class, 0, 1) !== '\\') {
$class = self::SORT_CLAUSE_NAMESPACE . $class;
}

if (class_exists($class)) {
/** @var \eZ\Publish\API\Repository\Values\Content\Query\SortClause $sortClause */
$sortClause = new $class(...$args);
$sortClause->direction = $direction;

return [$sortClause];
}

return [];
}

private function getRootLocationPathString(): string
{
$rootLocation = $this->repository->getLocationService()->loadLocation(
Expand Down
42 changes: 42 additions & 0 deletions eZ/Publish/Core/QueryType/BuildIn/SortClausesFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?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.
*/
declare(strict_types=1);

namespace eZ\Publish\Core\QueryType\BuildIn;

use eZ\Publish\Core\QueryType\BuildIn\SortSpec\SortClauseParserInterface;
use eZ\Publish\Core\QueryType\BuildIn\SortSpec\SortSpecLexer;
use eZ\Publish\Core\QueryType\BuildIn\SortSpec\SortSpecParser;

/**
* @internal
*/
final class SortClausesFactory implements SortClausesFactoryInterface
{
/** @var \eZ\Publish\Core\QueryType\BuildIn\SortSpec\SortClauseParserInterface */
private $sortClauseParser;

public function __construct(SortClauseParserInterface $sortClauseArgsParser)
{
$this->sortClauseParser = $sortClauseArgsParser;
}

/**
* @throws \eZ\Publish\Core\QueryType\BuildIn\SortSpec\Exception\SyntaxErrorException
*
* @return \eZ\Publish\API\Repository\Values\Content\Query\SortClause[]
*/
public function createFromSpecification(string $specification): array
{
$lexer = new SortSpecLexer();
$lexer->tokenize($specification);

$parser = new SortSpecParser($this->sortClauseParser, $lexer);

return $parser->parseSortClausesList();
}
}
22 changes: 22 additions & 0 deletions eZ/Publish/Core/QueryType/BuildIn/SortClausesFactoryInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?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.
*/
declare(strict_types=1);

namespace eZ\Publish\Core\QueryType\BuildIn;

/**
* @internal
*/
interface SortClausesFactoryInterface
{
/**
* @return \eZ\Publish\API\Repository\Values\Content\Query\SortClause[]
*
* @throws \eZ\Publish\Core\QueryType\BuildIn\SortSpec\Exception\SyntaxErrorException
*/
public function createFromSpecification(string $specification): array;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?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.
*/
declare(strict_types=1);

namespace eZ\Publish\Core\QueryType\BuildIn\SortSpec\Exception;

use eZ\Publish\Core\QueryType\BuildIn\SortSpec\Token;
use RuntimeException;

final class SyntaxErrorException extends RuntimeException
{
public static function fromUnexpectedToken(string $input, Token $token, array $expectedTypes): self
{
$message = sprintf(
'Error while parsing sorting specification: "%s": Unexpected token %s (%s) at position %d. Expected one of the following tokens: %s',
$input,
$token->getValue(),
$token->getType(),
$token->getPosition(),
implode(' ', $expectedTypes)
);

return new self($message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?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.
*/
declare(strict_types=1);

namespace eZ\Publish\Core\QueryType\BuildIn\SortSpec\Exception;

use eZ\Publish\Core\QueryType\BuildIn\SortSpec\SortClauseParserInterface;
use RuntimeException;
use Throwable;

final class UnsupportedSortClauseException extends RuntimeException
{
public function __construct(string $name, $code = 0, Throwable $previous = null)
{
$message = sprintf(
'Could not find %s for %s sort clause',
SortClauseParserInterface::class,
$name
);

parent::__construct($message, $code, $previous);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?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.
*/
declare(strict_types=1);

namespace eZ\Publish\Core\QueryType\BuildIn\SortSpec\SortClauseParser;

use eZ\Publish\API\Repository\Values\Content\Query\SortClause;
use eZ\Publish\Core\QueryType\BuildIn\SortSpec\Exception\UnsupportedSortClauseException;
use eZ\Publish\Core\QueryType\BuildIn\SortSpec\SortClauseParserInterface;
use eZ\Publish\Core\QueryType\BuildIn\SortSpec\SortSpecParserInterface;

/**
* Parser for sort clauses which expect only sort direction in constructor parameter.
*/
final class DefaultSortClauseParser implements SortClauseParserInterface
{
/** @var string[] */
private $valueObjectClassMap;

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

public function parse(SortSpecParserInterface $parser, string $name): SortClause
{
if (isset($this->valueObjectClassMap[$name])) {
$class = $this->valueObjectClassMap[$name];

return new $class($parser->parseSortDirection());
}

throw new UnsupportedSortClauseException($name);
}

public function supports(string $name): bool
{
return isset($this->valueObjectClassMap[$name]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?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.
*/
declare(strict_types=1);

namespace eZ\Publish\Core\QueryType\BuildIn\SortSpec\SortClauseParser;

use eZ\Publish\API\Repository\Values\Content\Query\SortClause;
use eZ\Publish\API\Repository\Values\Content\Query\SortClause\Field;
use eZ\Publish\Core\QueryType\BuildIn\SortSpec\SortClauseParserInterface;
use eZ\Publish\Core\QueryType\BuildIn\SortSpec\SortSpecParserInterface;
use eZ\Publish\Core\QueryType\BuildIn\SortSpec\Token;

/**
* Parser for \eZ\Publish\API\Repository\Values\Content\Query\SortClause\Field sort clause.
*/
final class FieldSortClauseParser implements SortClauseParserInterface
{
private const SUPPORTED_CLAUSE_NAME = 'field';

public function parse(SortSpecParserInterface $parser, string $name): SortClause
{
$args = [];
$args[] = $parser->match(Token::TYPE_ID)->getValue();
$parser->match(Token::TYPE_DOT);
$args[] = $parser->match(Token::TYPE_ID)->getValue();
$args[] = $parser->parseSortDirection();

return new Field(...$args);
}

public function supports(string $name): bool
{
return $name === self::SUPPORTED_CLAUSE_NAME;
}
}
Loading

0 comments on commit f636bc9

Please sign in to comment.