Sorting
, (*1)
The sorting package contains tools to sort data structures like tables or collections of objects., (*2)
Sorting instructions are objects and can therefore easily be passed along as parameters.
Multiple instructions can be chained in order to sort by several fields., (*3)
Installation
Install using composer:, (*4)
composer require stratadox/sorting, (*5)
Basic usage
2D Arrays
When sorting a collection of associative arrays, use:, (*6)
<?php
use Stratadox\Sorting\ArraySorter;
use Stratadox\Sorting\Sort;
$table = [
['name' => 'Foo', 'rating' => 3],
['name' => 'Bar', 'rating' => 1],
['name' => 'Baz', 'rating' => 2],
];
$sorter = new ArraySorter();
$table = $sorter->sort($table, Sort::descendingBy('rating'));
assert($table === [
['name' => 'Foo', 'rating' => 3],
['name' => 'Baz', 'rating' => 2],
['name' => 'Bar', 'rating' => 1],
]);
Objects
In case the collection consists of objects, the sorting definition remain the
same. Instead of using the ArraySorter, the ObjectSorter picks up the sorting
requirements., (*7)
<?php
use Stratadox\Sorting\ObjectSorter;
use Stratadox\Sorting\Sort;
class SomeObject
{
private $name;
private $rating;
public function __construct(string $name, int $rating)
{
$this->name = $name;
$this->rating = $rating;
}
public function name(): string
{
return $this->name;
}
public function rating(): int
{
return $this->rating;
}
}
$objects = [
new SomeObject('Foo', 3),
new SomeObject('Bar', 1),
new SomeObject('Baz', 2),
];
$sorter = new ObjectSorter();
$objects = $sorter->sort($objects, Sort::ascendingBy('rating'));
assert($objects == [
new SomeObject('Bar', 1),
new SomeObject('Baz', 2),
new SomeObject('Foo', 3),
]);
Method mapping
When the names of the methods do not correspond to the fields, or when a custom
method is to be used for determining the sorting weight, a mapping can be given
to the ObjectSorter:, (*8)
<?php
use Stratadox\Sorting\ObjectSorter;
use Stratadox\Sorting\Sort;
class SomeObject
{
private $name;
private $rating;
public function __construct(string $name, int $rating)
{
$this->name = $name;
$this->rating = $rating;
}
public function getName(): string
{
return $this->name;
}
public function getTheRating(): int
{
return $this->rating;
}
}
$objects = [
new SomeObject('Foo', 3),
new SomeObject('Bar', 1),
new SomeObject('Baz', 2),
];
$sorter = new ObjectSorter([
'name' => 'getName',
'rating' => 'getTheRating',
]);
$objects = $sorter->sort($objects, Sort::ascendingBy('rating'));
assert($objects == [
new SomeObject('Bar', 1),
new SomeObject('Baz', 2),
new SomeObject('Foo', 3),
]);
Nested arrays
If the structure to be sorted consists of a list of nested arrays, one can use
the NestedArraySorter:, (*9)
<?php
use Stratadox\Sorting\NestedArraySorter;
use Stratadox\Sorting\Sort;
$unsorted = [
['result' => ['index' => 3, 'label' => 'bar']],
['result' => ['index' => 2, 'label' => 'qux']],
['result' => ['index' => 1, 'label' => 'baz']],
['result' => ['index' => 2, 'label' => 'foo']],
];
$sorter = new NestedArraySorter();
$result = $sorter->sort($unsorted, Sort::ascendingBy('result.index'));
assert($result === [
['result' => ['index' => 1, 'label' => 'baz']],
['result' => ['index' => 2, 'label' => 'qux']],
['result' => ['index' => 2, 'label' => 'foo']],
['result' => ['index' => 3, 'label' => 'bar']],
]);
Sorting by multiple fields
<?php
use Stratadox\Sorting\ArraySorter;
use Stratadox\Sorting\Sort;
$table = [
['name' => 'Bar', 'rating' => 1],
['name' => 'Foo', 'rating' => 3],
['name' => 'Baz', 'rating' => 1],
];
$sorter = new ArraySorter();
$table = $sorter->sort($table,
Sort::descendingBy('rating', Sort::descendingBy('name'))
);
assert($table == [
['name' => 'Foo', 'rating' => 3],
['name' => 'Baz', 'rating' => 1],
['name' => 'Bar', 'rating' => 1],
]);
Or:, (*10)
<?php
use Stratadox\Sorting\ArraySorter;
use Stratadox\Sorting\Sort;
$table = [
['name' => 'Bar', 'rating' => 1],
['name' => 'Foo', 'rating' => 3],
['name' => 'Baz', 'rating' => 1],
];
$sorter = new ArraySorter();
$table = $sorter->sort($table,
Sort::descendingBy('rating')->andThenDescendingBy('name')
);
assert($table == [
['name' => 'Foo', 'rating' => 3],
['name' => 'Baz', 'rating' => 1],
['name' => 'Bar', 'rating' => 1],
]);
Or:, (*11)
<?php
use Stratadox\Sorting\ArraySorter;
use Stratadox\Sorting\Sorted;
$table = [
['name' => 'Bar', 'rating' => 1],
['name' => 'Foo', 'rating' => 3],
['name' => 'Baz', 'rating' => 1],
];
$sorter = new ArraySorter();
$table = $sorter->sort($table, Sorted::by([
'rating' => false,
'name' => false,
]));
assert($table == [
['name' => 'Foo', 'rating' => 3],
['name' => 'Baz', 'rating' => 1],
['name' => 'Bar', 'rating' => 1],
]);
Structure
The sorting package consists of two core concepts:
- the sorting definition, which is used to declare the sorting requirements, and
- the sorter, which sorts the elements according to the given definition, (*12)
This separation is maintained in order to decouple the intention of sorting from
the data structure that is being sorted., (*13)
A sorting definition, for instance Sort::ascendingBy('name'), can be applied
to a collection of associative arrays or to a collection of objects - the client
that asks for the sorting does not need to know the internals of the elements
that are to be sorted., (*14)
When designing an interface that allows for sorting, a method might accept a
Sorting definition, eg. public function getSorted(Sorting $definition): array.
Implementors of this interface may use any sorter they see fit, so long as they
produce the expected results., (*15)
Some implementations may not even use any sorter at all, but instead take the
sorting definition and produce a sorted list of results in a different way.
An example of such usage might be a repository with an in-memory and an sql
implementation:, (*16)
<?php
use Stratadox\Sorting\Contracts\Sorting;
use Stratadox\Sorting\ObjectSorter;
use Stratadox\Sorting\OrderByParser;
class SomeEntity
{
// ...
}
interface SomeRepository
{
/** @return SomeEntity[] */
public function all(Sorting $sorting): array;
}
class SomeInMemoryRepository implements SomeRepository
{
private $sorter;
private $entities;
public function __construct(SomeEntity ...$entities)
{
$this->sorter = new ObjectSorter();
$this->entities = $entities;
}
public function all(Sorting $sorting): array
{
return $this->sorter->sort($this->entities, $sorting);
}
}
class SomeDatabaseRepository implements SomeRepository
{
private $connection;
private $orderByParser;
private $deserializer;
public function __construct(PDO $connection, DeserializesObjects $deserializer)
{
$this->connection = $connection;
$this->orderByParser = OrderByParser::allowing('foo', 'bar');
$this->deserializer = $deserializer;
}
public function all(Sorting $sorting): array
{
$query = $this->connection->query(
'SELECT * FROM some_table ' . $this->orderByParser->parse($sorting)
);
$entities = [];
foreach ($query->fetchAll(PDO::FETCH_ASSOC) as $row) {
$entities[] = $this->deserializer->from($row);
}
return $entities;
}
}