Skip to content

Commit

Permalink
Refactor "Sorted" rule
Browse files Browse the repository at this point in the history
The sorted rule accepts a callback on its constructor that may be used
to filter values from inside the input. However, with the "Call" rule
one can archive almost the same result. Besides that particular
characteristic, its constructor accepts a boolean value to determine
whether the sorting is ascending or descending.

This commit will remove the callback from the constructor and replace
the boolean by a string which can be "ASC" or "DESC."

Along with those changes, this change will make a few more improvements:

- Make the exception message specific about the sorting direction;

- Allow the rule to validate also strings;

- Update documentation.

Co-authored-by: Danilo Correa <danilosilva87@gmail.com>
Signed-off-by: Henrique Moody <henriquemoody@gmail.com>
  • Loading branch information
henriquemoody and dcorrea777 committed Mar 10, 2019
1 parent 5444ab5 commit 1f6c821
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 155 deletions.
64 changes: 32 additions & 32 deletions docs/rules/Sorted.md
@@ -1,48 +1,48 @@
# Ordered
# Sorted

- `Sorted(callable $fn = null, bool $ascending = true)`
- `Sorted(string $direction)`

Validates if the input is Sorted
Validates whether the input is sorted in a certain order or not.

```php
v::sorted()->validate([1,2,3]); // true
v::sorted()->validate([1,6,3]); // false
v::sorted(null, false)->validate([3,2,1]); // true
v::sorted(function($x){
return $x['key'];
})->validate([
[
'key' => 1,
],
[
'key' => 5,
],
[
'key' => 9,
],
]); // true
v::sorted(function($x){
return $x['key'];
})->validate([
[
'key' => 1,
],
[
'key' => 7,
],
[
'key' => 4,
],
]); // false
v::sorted('ASC')->validate([1, 2, 3]); // true
v::sorted('ASC')->validate('ABC'); // true
v::sorted('DESC')->validate([3, 2, 1]); // true
v::sorted('ASC')->validate([]); // true
v::sorted('ASC')->validate([1]); // true
```

You can also combine [Call](Call.md) to create custom validations:

```php
v::call(
static function (array $input): array {
return array_column($input, 'key');
},
v::sorted('ASC')
)->validate([
['key' => 1],
['key' => 5],
['key' => 9],
]); // true

v::call('strval', v::sorted('DESC'))->validate(4321); // true

v::call('iterator_to_array', v::sorted())->validate(new ArrayIterator([1, 7, 4])); // false
```

## Changelog

Version | Description
--------|-------------
2.0.0 | Add support for strings
2.0.0 | Do not use array keys to sort
2.0.0 | Use sorting direction instead of boolean value
2.0.0 | Do not accept callback in the constructor
1.1.1 | Created

***
See also:

- [Call](Call.md)
- [ArrayVal](ArrayVal.md)
19 changes: 17 additions & 2 deletions library/Exceptions/SortedException.php
Expand Up @@ -13,21 +13,36 @@

namespace Respect\Validation\Exceptions;

use Respect\Validation\Rules\Sorted;

/**
* @author Henrique Moody <henriquemoody@gmail.com>
* @author Mikhail Vyrtsev <reeywhaar@gmail.com>
*/
final class SortedException extends ValidationException
{
public const ASCENDING = 'ascending';
public const DESCENDING = 'descending';

/**
* {@inheritdoc}
*/
public static $defaultTemplates = [
self::MODE_DEFAULT => [
self::STANDARD => '{{name}} must be ordered',
self::ASCENDING => '{{name}} must be sorted in ascending order',
self::DESCENDING => '{{name}} must be sorted in descending order',
],
self::MODE_NEGATIVE => [
self::STANDARD => '{{name}} must not be ordered',
self::ASCENDING => '{{name}} must not be sorted in ascending order',
self::DESCENDING => '{{name}} must not be sorted in descending order',
],
];

/**
* {@inheritdoc}
*/
protected function chooseTemplate(): string
{
return $this->getParam('direction') === Sorted::ASCENDING ? self::ASCENDING : self::DESCENDING;
}
}
72 changes: 54 additions & 18 deletions library/Rules/Sorted.php
Expand Up @@ -13,49 +13,85 @@

namespace Respect\Validation\Rules;

use Respect\Validation\Exceptions\ComponentException;
use function array_values;
use function count;
use function is_array;
use function is_string;
use function sprintf;
use function str_split;

/**
* Validates whether the input is sorted in a certain order or not.
*
* @author Henrique Moody <henriquemoody@gmail.com>
* @author Mikhail Vyrtsev <reeywhaar@gmail.com>
*/
final class Sorted extends AbstractRule
{
/**
* @var callable
*/
private $fn = null;
public const ASCENDING = 'ASC';
public const DESCENDING = 'DESC';

/**
* @var bool
* @var string
*/
private $ascending = true;
private $direction;

public function __construct(?callable $fn = null, bool $ascending = true)
public function __construct(string $direction)
{
$this->fn = $fn ?? static function ($x) {
return $x;
};
$this->ascending = $ascending;
if ($direction !== self::ASCENDING && $direction !== self::DESCENDING) {
throw new ComponentException(
sprintf('Direction should be either "%s" or "%s"', self::ASCENDING, self::DESCENDING)
);
}

$this->direction = $direction;
}

/**
* {@inheritdoc}
*/
public function validate($input): bool
{
$count = count($input);
if ($count < 2) {
return true;
if (!is_array($input) && !is_string($input)) {
return false;
}
for ($i = 1; $i < $count; ++$i) {
if (($this->ascending && ($this->fn)($input[$i]) < ($this->fn)($input[$i - 1]))
|| (!$this->ascending && ($this->fn)($input[$i]) > ($this->fn)($input[$i - 1]))
) {

$values = $this->getValues($input);
$count = count($values);
for ($position = 1; $position < $count; ++$position) {
if (!$this->isSorted($values[$position], $values[$position - 1])) {
return false;
}
}

return true;
}

/**
* @param mixed $current
* @param mixed $last
*/
private function isSorted($current, $last): bool
{
if ($this->direction === self::ASCENDING) {
return $current > $last;
}

return $current < $last;
}

/**
* @param string|mixed[] $input
*
* @return mixed[]
*/
private function getValues($input): array
{
if (is_array($input)) {
return array_values($input);
}

return str_split($input);
}
}
1 change: 1 addition & 0 deletions library/Validator.php
Expand Up @@ -148,6 +148,7 @@
* @method static Validator sf(Constraint $constraint, ValidatorInterface $validator = null)
* @method static Validator size(string $minSize = null, string $maxSize = null)
* @method static Validator slug()
* @method static Validator sorted(string $direction)
* @method static Validator space(string ...$additionalChars)
* @method static Validator startsWith($startValue, bool $identical = false)
* @method static Validator stringType()
Expand Down
71 changes: 71 additions & 0 deletions tests/integration/rules/sorted.phpt
@@ -0,0 +1,71 @@
--CREDITS--
Danilo Correa <danilosilva87@gmail.com>
Henrique Moody <henriquemoody@gmail.com>
--FILE--
<?php

declare(strict_types=1);

require 'vendor/autoload.php';

use Respect\Validation\Exceptions\NestedValidationException;
use Respect\Validation\Exceptions\SortedException;
use Respect\Validation\Validator as v;

try {
v::sorted('ASC')->check([1, 3, 2]);
} catch (SortedException $exception) {
echo $exception->getMessage().PHP_EOL;
}

try {
v::sorted('DESC')->check([1, 2, 3]);
} catch (SortedException $exception) {
echo $exception->getMessage().PHP_EOL;
}

try {
v::not(v::sorted('ASC'))->check([1, 2, 3]);
} catch (SortedException $exception) {
echo $exception->getMessage().PHP_EOL;
}

try {
v::not(v::sorted('DESC'))->check([3, 2, 1]);
} catch (SortedException $exception) {
echo $exception->getMessage().PHP_EOL;
}

try {
v::sorted('ASC')->assert([3, 2, 1]);
} catch (NestedValidationException $exception) {
echo $exception->getFullMessage().PHP_EOL;
}

try {
v::sorted('DESC')->assert([1, 2, 3]);
} catch (NestedValidationException $exception) {
echo $exception->getFullMessage().PHP_EOL;
}

try {
v::not(v::sorted('ASC'))->assert([1, 2, 3]);
} catch (NestedValidationException $exception) {
echo $exception->getFullMessage().PHP_EOL;
}

try {
v::not(v::sorted('DESC'))->assert([3, 2, 1]);
} catch (NestedValidationException $exception) {
echo $exception->getFullMessage().PHP_EOL;
}
?>
--EXPECT--
`{ 1, 3, 2 }` must be sorted in ascending order
`{ 1, 2, 3 }` must be sorted in descending order
`{ 1, 2, 3 }` must not be sorted in ascending order
`{ 3, 2, 1 }` must not be sorted in descending order
- `{ 3, 2, 1 }` must be sorted in ascending order
- `{ 1, 2, 3 }` must be sorted in descending order
- `{ 1, 2, 3 }` must not be sorted in ascending order
- `{ 3, 2, 1 }` must not be sorted in descending order

0 comments on commit 1f6c821

Please sign in to comment.