Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Locale-aware & multi-key sorting of arrays
<?php
if (! function_exists('lsort')) {
/**
* Sort an array according to the current locale.
*
* Order will be determined by given key or keys, which may be nested using dot notation.
* Alternatively a callback may be given to return value or array of values to sort with.
* Boolean may be supplied as a last argument to specify sort order, true for descending.
*
* More on locale sorting http://demo.icu-project.org/icu-bin/locexp?_=en_US&d_=en&x=col.
*
* @param array $array
* @param \Closure $callback
* @param array|string ...$keys
* @param bool $descending
* @return bool
*/
function lsort(array &$array, ...$args) {
// We will determine the sort order from the last argument if it is a boolean. Then we will
// define a callback closure for extracting values for comparison from items of the array.
if (count($args) > 0 && is_bool(end($args))) {
$descending = array_pop($args);
}
else {
$descending = false;
}
if (count($args) == 0) {
$callback = null;
}
else if (count($args) == 1 && is_closure(reset($args))) {
$callback = array_shift($args); }
else {
$callback = function ($item) use ($args) {
$result = [];
foreach (array_flatten($args) as $key) {
$result[] = data_get($item, $key);
}
return $result;
};
}
// To reduce number of callback calls we will first prepare array of comparator values for each
// element in the sorted array. We will then sort these values and in the end restore original.
$values = array_map(function ($item) use ($callback) {
if (! $callback || ! is_array($item = $callback($item))) {
return [$item];
}
return $item;
}, $array);
// We will create a collator in current locale and enable sorting numbers in the natural order.
// Then we will sort the array by comparing consecutive values returned from callback for every
// item until we find a difference or run out of values. We'll optionally reverse the order.
$collator = new Collator(config('app.locale'));
$collator->setAttribute(Collator::NUMERIC_COLLATION, Collator::ON);
$result = uasort($values, function ($A, $B) use ($collator, $descending) {
while (! empty($A) && ! empty($B)) {
$a = array_shift($A);
$b = array_shift($B);
if (is_numeric($a) && is_numeric($b) || $a instanceof DateTime && $b instanceof DateTime) {
$result = $a < $b ? -1 : ($a > $b ? 1 : 0);
}
else {
$result = $collator->compare($a, $b);
}
if ($result) {
return $descending ? - $result : $result;
}
}
return 0;
});
$array = array_replace($values, $array);
return $result;
}
}
if (! function_exists('lrsort')) {
/**
* Sort an array in descending order according to the current locale.
*
* @param array $array
* @param \Closure $callback
* @param array|string ...$keys
* @return bool
*/
function lrsort(&$array, ...$args) {
array_push($args, true);
return lsort($array, ...$args);
}
}
<?php
namespace App\Providers;
use Illuminate\Support\Collection;
use Illuminate\Support\ServiceProvider;
class MacroServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerLocaleSortOnCollection();
}
/**
* Register localeSort and localeSortDesc macros on Collection class.
*
* @return void
*/
protected function registerLocaleSortOnCollection()
{
Collection::macro('localeSort', function (...$args) {
$items = $this->items;
lsort($items, ...$args);
return new static($items);
});
Collection::macro('localeSortDesc', function (...$args) {
$items = $this->items;
lrsort($items, ...$args);
return new static($items);
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment