1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-23 14:56:51 +02:00

Update WireArray::sort to sort more like PHP's sort so that null or blank string values are sorted rather than appended. Add a SORT_APPEND_NULLS flag for the previous behavior too. @Toutouwai brought this one up before but I can't seem to find where it was to reference it here.

This commit is contained in:
Ryan Cramer
2022-02-10 14:00:50 -05:00
parent a5c70a4e7d
commit 598a3a7df5

View File

@@ -36,7 +36,6 @@
* #pw-body * #pw-body
* *
*/ */
class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Countable { class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Countable {
/** /**
@@ -1319,16 +1318,19 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count
protected function _sort($properties, $numNeeded = null) { protected function _sort($properties, $numNeeded = null) {
// string version is used for change tracking // string version is used for change tracking
$propertiesStr = is_array($properties) ? implode(',', $properties) : $properties; $isArray = is_array($properties);
if(!is_array($properties)) $properties = preg_split('/\s*,\s*/', $properties); $propertiesStr = $isArray ? implode(',', $properties) : $properties;
if(!$isArray) $properties = explode(',', $properties);
if(empty($properties)) return $this;
// shortcut for random (only allowed as the sole sort property) // shortcut for random (only allowed as the sole sort property)
// no warning/error for issuing more properties though // no warning/error for issuing more properties though
// TODO: warning for random+more properties (and trackChange() too) // TODO: warning for random+more properties (and trackChange() too)
if($properties[0] == 'random') return $this->shuffle(); if($properties[0] === 'random') return $this->shuffle();
$data = $this->stableSort($this, $properties, $numNeeded); $data = $this->stableSort($this, $properties, $numNeeded);
$this->trackChange("sort:$propertiesStr", $this->data, $data); if($this->trackChanges) $this->trackChange("sort:$propertiesStr", $this->data, $data);
$this->data = $data; $this->data = $data;
return $this; return $this;
@@ -1345,6 +1347,10 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count
* - `SORT_LOCALE_STRING` compare items as strings, based on the current locale * - `SORT_LOCALE_STRING` compare items as strings, based on the current locale
* - `SORT_NATURAL` compare items as strings using “natural ordering” like natsort() * - `SORT_NATURAL` compare items as strings using “natural ordering” like natsort()
* - `SORT_FLAG_CASE` can be combined (bitwise OR) with SORT_STRING or SORT_NATURAL to sort strings case-insensitively * - `SORT_FLAG_CASE` can be combined (bitwise OR) with SORT_STRING or SORT_NATURAL to sort strings case-insensitively
* - `SORT_APPEND_NULLS` can be used on its own or combined with any of above (bitwise OR) to specify that null
* or blank values should be treated as unsortable and appended to the end of the sortable set rather than sorted as
* blank values. This duplicates the behavior prior to 3.0.194 (available only in 3.0.194+). Note that this flag
* is unique to ProcessWire only and is not in PHP.
* *
* For more details, see `$sort_flags` argument at: https://www.php.net/manual/en/function.sort.php * For more details, see `$sort_flags` argument at: https://www.php.net/manual/en/function.sort.php
* *
@@ -1372,22 +1378,28 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count
*/ */
protected function stableSort(&$data, $properties, $numNeeded = null) { protected function stableSort(&$data, $properties, $numNeeded = null) {
$property = array_shift($properties); $property = trim(array_shift($properties));
$nullable = array();
$unidentified = array();
$sortable = array(); $sortable = array();
$reverse = false; $reverse = false;
$subProperty = ''; $subProperty = '';
$sortFlags = $this->sortFlags;
if(substr($property, 0, 1) == '-' || substr($property, -1) == '-') { $sortNulls = true;
$reverse = true;
$property = trim($property, '-'); if($sortFlags >= SORT_APPEND_NULLS && ($sortFlags & SORT_APPEND_NULLS)) {
$sortNulls = false;
$sortFlags -= SORT_APPEND_NULLS;
}
$pos = strpos($property, '-');
if($pos !== false && ($pos === 0 || substr($property, -1) == '-')) {
$reverse = true;
$property = trim($property, '-');
} }
$pos = strpos($property, "."); $pos = strpos($property, '.');
if($pos) { if($pos) {
$subProperty = substr($property, $pos+1); list($property, $subProperty) = explode('.', $property, 2);
$property = substr($property, 0, $pos);
} }
foreach($data as $item) { foreach($data as $item) {
@@ -1399,34 +1411,55 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count
$key = $this->getItemPropertyValue($key, $subProperty); $key = $this->getItemPropertyValue($key, $subProperty);
} }
// check for items that resolve to blank if($key === null) {
if(is_null($key) || (is_string($key) && !strlen(trim($key)))) { if($sortNulls) {
// @todo option to allow for these to be sorted regularly $key = "\0"; // sort as ascii null
$unidentified[] = $item; } else {
continue; $nullable[] = $item;
continue;
}
} else if($key === false) {
$key = 0;
} else if($key === true) {
$key = 1;
} else if(is_int($key)) {
// ok
} else if(ctype_digit("$key")) {
$key = (int) $key;
} else {
$key = (string) $key;
if(trim($key) === '') {
if($sortNulls) {
$key = ' '; // ensure sort value higher than \0
} else {
$nullable[] = $item;
continue;
}
}
} }
$key = (string) $key;
// ensure numeric sorting if the key is a number
if(ctype_digit("$key")) $key = (int) $key;
if(isset($sortable[$key])) { if(isset($sortable[$key])) {
// key resolved to the same value that another did, so keep them together by converting this index to an array // key resolved to the same value that another did, so keep them together by converting this index to an array
// this makes the algorithm stable (for equal keys the order would be undefined) // this makes the algorithm stable (for equal keys the order would be undefined)
if(is_array($sortable[$key])) $sortable[$key][] = $item; if(is_array($sortable[$key])) {
else $sortable[$key] = array($sortable[$key], $item); $sortable[$key][] = $item;
} else {
$sortable[$key] = array($sortable[$key], $item);
}
} else { } else {
$sortable[$key] = $item; $sortable[$key] = $item;
} }
} }
// sort the items by the keys we collected // sort the items by the keys we collected
if($reverse) krsort($sortable, $this->sortFlags); if($reverse) {
else ksort($sortable, $this->sortFlags); krsort($sortable, $sortFlags);
} else {
ksort($sortable, $sortFlags);
}
// add the items that resolved to no key to the end, as an array // add the items that resolved to no key to the end, as an array
$sortable[] = $unidentified; if(!empty($nullable)) $sortable[] = $nullable;
// restore sorted array to lose sortable keys and restore proper keys // restore sorted array to lose sortable keys and restore proper keys
$a = array(); $a = array();
@@ -1435,7 +1468,9 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count
// if more properties to sort by exist, use them for this sub-array // if more properties to sort by exist, use them for this sub-array
$n = null; $n = null;
if($numNeeded) $n = $numNeeded - count($a); if($numNeeded) $n = $numNeeded - count($a);
if(count($properties)) $value = $this->stableSort($value, $properties, $n); if(count($properties)) {
$value = $this->stableSort($value, $properties, $n);
}
foreach($value as $k => $v) { foreach($value as $k => $v) {
$newKey = $this->getItemKey($v); $newKey = $this->getItemKey($v);
$a[$newKey] = $v; $a[$newKey] = $v;
@@ -2658,3 +2693,5 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count
return $a; return $a;
} }
} }
define('SORT_APPEND_NULLS', 32);