Skip to content
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
9 changes: 8 additions & 1 deletion lib/Core/Field/OrderFieldType.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use Rollerworks\Component\Search\Extension\Core\DataTransformer\OrderToLocalizedTransformer;
use Rollerworks\Component\Search\Extension\Core\DataTransformer\OrderTransformer;
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;

Expand All @@ -32,6 +33,7 @@ public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'default' => null,
'always' => null,
'case' => OrderTransformer::CASE_UPPERCASE,
'alias' => ['ASC' => 'ASC', 'DESC' => 'DESC'],
'view_label' => ['ASC' => 'asc', 'DESC' => 'desc'],
Expand All @@ -44,9 +46,10 @@ public function configureOptions(OptionsResolver $resolver): void
OrderTransformer::CASE_LOWERCASE,
OrderTransformer::CASE_UPPERCASE,
]);
$resolver->setAllowedValues('default', ['asc', 'desc', 'ASC', 'DESC', null]);
$resolver->setAllowedValues('always', ['append', 'prepend', null]);
$resolver->setAllowedTypes('alias', 'array');
$resolver->setAllowedTypes('view_label', ['array']);
$resolver->setAllowedTypes('default', ['null', 'string']);
$resolver->setAllowedTypes('type', ['string', 'null']);
$resolver->setAllowedTypes('type_options', ['array']);
$resolver->setAllowedTypes('label', ['null', 'string']);
Expand Down Expand Up @@ -85,6 +88,10 @@ public function configureOptions(OptionsResolver $resolver): void

public function buildType(FieldConfig $config, array $options): void
{
if ($options['always'] !== null && $options['default'] === null) {
throw new InvalidOptionsException('Setting "always" requires the "default" option is set with a direction.');
}

$config->setNormTransformer(new OrderTransformer($options['alias'], $options['case']));
$config->setViewTransformer(new OrderToLocalizedTransformer($options['alias'], $options['view_label'], $options['case']));
}
Expand Down
48 changes: 48 additions & 0 deletions lib/Core/Input/AbstractInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

use Rollerworks\Component\Search\ErrorList;
use Rollerworks\Component\Search\InputProcessor;
use Rollerworks\Component\Search\SearchCondition;
use Rollerworks\Component\Search\SearchOrder;

/**
* AbstractInput provides the shared logic for the InputProcessors.
Expand All @@ -35,6 +37,52 @@ public function __construct(?Validator $validator = null)
$this->validator = $validator ?? new NullValidator();
}

/**
* Finalize the ordering of the fields.
*
* - Sets the default ordering if no ordering is set.
* - Ensures that the always-ordered fields are present.
*/
public static function finalizeOrdering(SearchCondition $condition): void
{
$ordering = $condition->getOrder()?->getFields() ?? [];
$hasOrder = $ordering !== [];

$apply = $hasOrder;
$prepend = $append = [];

foreach ($condition->getFieldSet()->all() as $field) {
$name = $field->getName();

if (! $condition->getFieldSet()->isOrder($name)) {
continue;
}

$default = $field->getOption('default');

if ($default === null) {
continue;
}

$always = $field->getOption('always');

if ($always === 'prepend') {
$prepend[$name] = $default;
$apply = true;
} elseif ($always === 'append') {
$append[$name] = $default;
$apply = true;
} elseif (! $hasOrder) {
$ordering[$name] = $default;
$apply = true;
}
}

if ($apply) {
$condition->setOrder(new SearchOrder($ordering, $prepend, $append));
}
}

/**
* This method is called after processing and helps with finding bugs.
*/
Expand Down
2 changes: 2 additions & 0 deletions lib/Core/Input/JsonInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ public function process(ProcessorConfig $config, $input): SearchCondition
throw new InvalidSearchConditionException($errors);
}

self::finalizeOrdering($condition);

return $condition;
}

Expand Down
2 changes: 2 additions & 0 deletions lib/Core/Input/StringInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ public function process(ProcessorConfig $config, $input): SearchCondition
throw new InvalidSearchConditionException($errors);
}

self::finalizeOrdering($condition);

return $condition;
}

Expand Down
3 changes: 3 additions & 0 deletions lib/Core/SearchConditionBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Rollerworks\Component\Search\Exception\BadMethodCallException;
use Rollerworks\Component\Search\Exception\InvalidArgumentException;
use Rollerworks\Component\Search\Field\OrderField;
use Rollerworks\Component\Search\Input\AbstractInput;
use Rollerworks\Component\Search\Value\ValuesGroup;

final class SearchConditionBuilder
Expand Down Expand Up @@ -273,6 +274,8 @@ public function getSearchCondition(): SearchCondition

$searchCondition->setPrimaryCondition($this->getPrimaryCondition());

AbstractInput::finalizeOrdering($searchCondition);

return $searchCondition;
}

Expand Down
116 changes: 97 additions & 19 deletions lib/Core/SearchOrder.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,42 @@
/**
* @author Dalibor Karlović <dalibor@flexolabs.io>
* @author Sebastiaan Stok <s.stok@rollerscapes.net>
*
* @psalm-type Sorting = array<string, 'asc'|'desc'>
*/
final class SearchOrder
{
/** @var array <string, 'desc'|'asc'> */
/** @psalm-var Sorting */
private readonly array $fields;

/** @psalm-var Sorting */
private readonly array $prepend;

/** @psalm-var Sorting */
private readonly array $append;

/** @psalm-var Sorting */
private array $finalSorting;

private readonly ValuesGroup $valuesGroup;

/**
* Creates a new SearchOrder.
*
* The $prepend fields always appear first in the sorting order.
* The $append fields always appear last in the sorting order.
*
* Any fields added with $values will always overwrite the values provided
* in $prepend and $append, while keeping there original position.
*
* @param ValuesGroup|array<string, 'desc'|'asc'|'DESC'|'ASC'> $values
* @param array<string, 'desc'|'asc'|'DESC'|'ASC'> $prepend
* @param array<string, 'desc'|'asc'|'DESC'|'ASC'> $append
*/
public function __construct(
ValuesGroup | array $values,
array $prepend = [],
array $append = [],
) {
if ($values instanceof ValuesGroup) {
trigger_deprecation('rollerworks/search', '2.0-BETA14', 'Passing a "%s" to "%s()" is deprecated, pass an associative array fields and there directions instead.', ValuesGroup::class, __METHOD__);
Expand All @@ -55,7 +78,71 @@ public function __construct(
$values = $fields;
}

$this->prepend = $this->processFields($prepend);
$this->fields = $this->processFields($values);
$this->append = $this->processFields($append);
$this->finalSorting = array_merge($this->prepend, $this->fields);

// Only append fields that are not already in prepend or fields
$this->finalSorting += $this->append;

$valuesGroup = new ValuesGroup();

foreach ($this->finalSorting as $fieldName => $direction) {
$valuesGroup->addField($fieldName, (new ValuesBag())->addSimpleValue($direction));
}
$this->valuesGroup = $valuesGroup;
}

public function getValuesGroup(): ValuesGroup
{
return $this->valuesGroup;
}

/**
* @psalm-return Sorting
*/
public function getFields(): array
{
return $this->fields;
}

/**
* @psalm-return Sorting
*/
public function getAppend(): array
{
return $this->append;
}

/**
* @psalm-return Sorting
*/
public function getPrepend(): array
{
return $this->prepend;
}

/**
* Gets the final sorting order.
*
* This is the final sorting order, which is the combination fields that are
* always-sorted fields and fields provided by user-input.
*
* @psalm-return Sorting
*/
public function getSorting(): array
{
return $this->finalSorting;
}

/**
* @param array<string, mixed> $values
*
* @psalm-return Sorting
*/
private function processFields(array $values): array
{
$fields = [];

foreach ($values as $fieldName => $direction) {
Expand All @@ -64,33 +151,24 @@ public function __construct(
}

if (! \is_string($direction)) {
throw new InvalidArgumentException(\sprintf('Field "%s" direction must be a string.', $fieldName));
throw new InvalidArgumentException(\sprintf('Field "%s" direction must be a string, "%s" given.', $fieldName, get_debug_type($direction)));
}

$direction = mb_strtolower($direction);

if (! \in_array($direction, ['desc', 'asc'], true)) {
throw new InvalidArgumentException(\sprintf('Invalid direction provided "%s" for field "%s", must be either "asc" or "desc" (case insensitive).', $direction, $fieldName));
throw new InvalidArgumentException(
\sprintf(
'Invalid direction provided "%s" for field "%s", must be either "asc" or "desc" (case insensitive).',
$direction,
$fieldName
)
);
}

$valuesGroup->addField($fieldName, (new ValuesBag())->addSimpleValue($direction));
$fields[$fieldName] = $direction;
}

$this->fields = $fields;
$this->valuesGroup = $valuesGroup;
}

public function getValuesGroup(): ValuesGroup
{
return $this->valuesGroup;
}

/**
* @return array<string, 'desc'|'asc'>
*/
public function getFields(): array
{
return $this->fields;
return $fields;
}
}
14 changes: 14 additions & 0 deletions lib/Core/Tests/Field/OrderFieldTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Rollerworks\Component\Search\Field\OrderFieldType;
use Rollerworks\Component\Search\Test\FieldTransformationAssertion;
use Rollerworks\Component\Search\Test\SearchIntegrationTestCase;
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;

/**
* @internal
Expand Down Expand Up @@ -254,4 +255,17 @@ public function it_fails_to_transform_with_invalid_direction(): void
)
;
}

/**
* @test
*/
public function error_with_always_append_and_no_default_value(): void
{
$this->expectException(InvalidOptionsException::class);
$this->expectExceptionMessage('Setting "always" requires the "default" option is set with a direction.');

$this->getFactory()->createField('@id', OrderFieldType::class, [
'always' => 'append',
]);
}
}
3 changes: 2 additions & 1 deletion lib/Core/Tests/Input/InputProcessorTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ protected function getFieldSet(bool $build = true, bool $order = false)
$fieldSet->add('date', DateType::class, ['pattern' => 'MM-dd-yyyy']);

if ($order) {
$fieldSet->add('@date', OrderFieldType::class, ['case' => OrderTransformer::CASE_LOWERCASE, 'alias' => ['up' => 'ASC', 'down' => 'DESC'], 'default' => 'down']);
$fieldSet->add('@date', OrderFieldType::class, ['case' => OrderTransformer::CASE_LOWERCASE, 'alias' => ['up' => 'ASC', 'down' => 'DESC'], 'default' => 'desc']);
$fieldSet->add('@id', OrderFieldType::class, ['default' => 'ASC']);
}

$fieldSet->set(
$this->getFactory()->createField('no-range-field', IntegerType::class)
->setValueTypeSupport(Range::class, false)
Expand Down
Loading
Loading