499 lines
17 KiB
PHP
499 lines
17 KiB
PHP
<?php
|
|
namespace nur\sery;
|
|
|
|
use ArrayAccess;
|
|
use Traversable;
|
|
|
|
/**
|
|
* Class cl: gestion de tableaux ou d'instances de {@link ArrayAccess} le cas
|
|
* échéant
|
|
*
|
|
* contrairement à {@link A}, les méthodes de cette classes sont plutôt conçues
|
|
* pour retourner un nouveau tableau
|
|
*/
|
|
class cl {
|
|
/** retourner un array non null à partir de $array */
|
|
static final function with($array): array {
|
|
if (is_array($array)) return $array;
|
|
elseif ($array === null || $array === false) return [];
|
|
elseif ($array instanceof Traversable) return iterator_to_array($array);
|
|
else return [$array];
|
|
}
|
|
|
|
/** retourner un array à partir de $array, ou null */
|
|
static final function withn($array): ?array {
|
|
if (is_array($array)) return $array;
|
|
elseif ($array === null || $array === false) return null;
|
|
elseif ($array instanceof Traversable) return iterator_to_array($array);
|
|
else return [$array];
|
|
}
|
|
|
|
/** tester si $array a au moins une clé numérique */
|
|
static final function have_num_keys(?array $array): bool {
|
|
if ($array === null) return false;
|
|
foreach ($array as $key => $value) {
|
|
if (is_int($key)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* tester si $array est une liste, c'est à dire un tableau non null avec
|
|
* uniquement des clés numériques séquentielles commençant à zéro
|
|
*
|
|
* NB: is_list(null) === false
|
|
* et is_list([]) === true
|
|
*/
|
|
static final function is_list(?array $array): bool {
|
|
if ($array === null) return false;
|
|
$index = -1;
|
|
foreach ($array as $key => $value) {
|
|
++$index;
|
|
if ($key !== $index) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* tester si $array contient la clé $key
|
|
*
|
|
* @param array|ArrayAccess $array
|
|
*/
|
|
static final function has($array, $key): bool {
|
|
if (is_array($array)) {
|
|
return array_key_exists($key, $array);
|
|
} elseif ($array instanceof ArrayAccess) {
|
|
return $array->offsetExists($key);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* retourner $array[$key] ou $default si la clé n'existe pas
|
|
*
|
|
* @param array|ArrayAccess $array
|
|
*/
|
|
static final function get($array, $key, $default=null) {
|
|
if (is_array($array)) {
|
|
if (array_key_exists($key, $array)) return $array[$key];
|
|
} elseif ($array instanceof ArrayAccess) {
|
|
if ($array->offsetExists($key)) return $array->offsetGet($key);
|
|
}
|
|
return $default;
|
|
}
|
|
|
|
/**
|
|
* si $array est un array ou une instance de ArrayAccess, créer ou modifier
|
|
* l'élément dont la clé est $key
|
|
*
|
|
* @param array|ArrayAccess $array
|
|
*/
|
|
static final function set(&$array, $key, $value): void {
|
|
if (is_array($array) || $array === null) {
|
|
if ($key === null) $array[] = $value;
|
|
else $array[$key] = $value;
|
|
} elseif ($array instanceof ArrayAccess) {
|
|
$array->offsetSet($key, $value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* si $array est un array ou une instance de ArrayAccess, supprimer l'élément
|
|
* dont la clé est $key
|
|
*
|
|
* @param array|ArrayAccess $array
|
|
*/
|
|
static final function del(&$array, $key): void {
|
|
if (is_array($array)) {
|
|
unset($array[$key]);
|
|
} elseif ($array instanceof ArrayAccess) {
|
|
$array->offsetUnset($key);
|
|
}
|
|
}
|
|
|
|
/** retourner le nombre d'éléments de $array */
|
|
static final function count(?array $array): int {
|
|
return $array !== null? count($array): 0;
|
|
}
|
|
|
|
/** retourner la liste des clés de $array */
|
|
static final function keys(?array $array): array {
|
|
return $array !== null? array_keys($array): [];
|
|
}
|
|
|
|
/**
|
|
* retourner la première valeur de $array ou $default si le tableau est null
|
|
* ou vide
|
|
*/
|
|
static final function first($array, $default=null) {
|
|
if (is_array($array)) return $array[array_key_first($array)];
|
|
return $default;
|
|
}
|
|
|
|
#############################################################################
|
|
|
|
/**
|
|
* Fusionner tous les tableaux spécifiés. Les valeurs null sont ignorées.
|
|
* Retourner null si aucun tableau n'est fourni ou s'ils étaient tous null.
|
|
*/
|
|
static final function merge(...$arrays): ?array {
|
|
$merges = [];
|
|
foreach ($arrays as $array) {
|
|
A::ensure_narray($array);
|
|
if ($array !== null) $merges[] = $array;
|
|
}
|
|
return $merges? array_merge(...$merges): null;
|
|
}
|
|
|
|
#############################################################################
|
|
|
|
/**
|
|
* vérifier que le chemin $keys existe dans le tableau $array
|
|
*
|
|
* si $pkey est vide ou null, retourner true
|
|
*/
|
|
static final function phas($array, $pkey): bool {
|
|
# optimisations
|
|
if ($pkey === null || $pkey === []) {
|
|
return true;
|
|
} elseif (is_int($pkey) || (is_string($pkey) && strpos($pkey, ".") === false)) {
|
|
return self::has($array, $pkey);
|
|
} elseif (!is_array($pkey)) {
|
|
$pkey = explode(".", strval($pkey));
|
|
}
|
|
# phas
|
|
$first = true;
|
|
foreach($pkey as $key) {
|
|
if ($key === "" && $first) {
|
|
# une chaine vide en première position est ignorée
|
|
continue;
|
|
} elseif (is_array($array)) {
|
|
if (!array_key_exists($key, $array)) return false;
|
|
$array = $array[$key];
|
|
} elseif ($array instanceof ArrayAccess) {
|
|
if (!$array->offsetExists($key)) return false;
|
|
$array = $array->offsetGet($key);
|
|
} else {
|
|
return false;
|
|
}
|
|
$first = false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static final function each_phas($array, ?array $pkeys): array {
|
|
$result = [];
|
|
if ($pkeys !== null) {
|
|
foreach ($pkeys as $pkey) {
|
|
$result[] = self::phas($array, $pkey);
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* obtenir la valeur correspondant au chemin $keys dans $array
|
|
*
|
|
* si $pkey est vide ou null, retourner $default
|
|
*/
|
|
static final function pget($array, $pkey, $default=null) {
|
|
# optimisations
|
|
if ($pkey === null || $pkey === []) return $default;
|
|
elseif ($pkey === "") return $array;
|
|
elseif (is_int($pkey) || (is_string($pkey) && strpos($pkey, ".") === false)) {
|
|
return self::get($array, $pkey, $default);
|
|
} elseif (!is_array($pkey)) {
|
|
$pkey = explode(".", strval($pkey));
|
|
}
|
|
# pget
|
|
$value = $array;
|
|
$first = true;
|
|
foreach($pkey as $key) {
|
|
if ($key === "" && $first) {
|
|
# une chaine vide en première position est ignorée
|
|
continue;
|
|
} elseif (is_array($value)) {
|
|
if (!array_key_exists($key, $value)) return $default;
|
|
$value = $value[$key];
|
|
} elseif ($value instanceof ArrayAccess) {
|
|
if (!$value->offsetExists($key)) return $default;
|
|
$value = $value->offsetGet($key);
|
|
} else {
|
|
return $default;
|
|
}
|
|
$first = false;
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
static final function each_pget($array, ?array $pkeys): array {
|
|
$result = [];
|
|
if ($pkeys !== null) {
|
|
foreach ($pkeys as $key => $pkey) {
|
|
$result[$key] = self::pget($array, $pkey);
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* modifier la valeur au chemin de clé $keys dans le tableau $array
|
|
*
|
|
* utiliser la clé "" (chaine vide) en dernière position pour rajouter à la fin, e.g
|
|
* - pset($array, [""], $value) est équivalent à $array[] = $value
|
|
* - pset($array, ["a", "b", ""], $value) est équivalent à $array["a"]["b"][] = $value
|
|
* la clé "" n'a pas de propriété particulière quand elle n'est pas en dernière position
|
|
*
|
|
* si $pkey est vide ou null, $array est remplacé par $value
|
|
*/
|
|
static final function pset(&$array, $pkey, $value): void {
|
|
# optimisations
|
|
if ($pkey === null || $pkey === []) {
|
|
$array = $value;
|
|
return;
|
|
} elseif ($pkey === "") {
|
|
$array[] = $value;
|
|
return;
|
|
} elseif (is_int($pkey) || (is_string($pkey) && strpos($pkey, ".") === false)) {
|
|
self::set($array, $pkey, $value);
|
|
return;
|
|
} elseif (!is_array($pkey)) {
|
|
$pkey = explode(".", strval($pkey));
|
|
}
|
|
# pset
|
|
A::ensure_array($array);
|
|
$current =& $array;
|
|
$key = null;
|
|
$last = count($pkey) - 1;
|
|
$i = 0;
|
|
foreach ($pkey as $key) {
|
|
if ($i == $last) break;
|
|
if ($current instanceof ArrayAccess) {
|
|
if (!$current->offsetExists($key)) $current->offsetSet($key, []);
|
|
$current =& $current->offsetGet($key);
|
|
if ($current === null) {
|
|
$current = [];
|
|
} elseif (!is_array($current) && !($current instanceof ArrayAccess)) {
|
|
$current = [$current];
|
|
}
|
|
} else {
|
|
A::ensure_array($current[$key]);
|
|
$current =& $current[$key];
|
|
}
|
|
$i++;
|
|
}
|
|
if ($key === "") $current[] = $value;
|
|
else $current[$key] = $value;
|
|
}
|
|
|
|
static final function each_pset(&$array, ?array $values): void {
|
|
if ($values !== null) {
|
|
foreach ($values as $pkey => $value) {
|
|
self::pset($array, $pkey, $value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* supprimer la valeur au chemin de clé $keys dans $array
|
|
*
|
|
* si $array vaut null ou false, sa valeur est inchangée.
|
|
* si $pkey est vide ou null, $array devient null
|
|
*/
|
|
static final function pdel(&$array, $pkey): void {
|
|
# optimisations
|
|
if ($array === null || $array === false) {
|
|
return;
|
|
} elseif ($pkey === null || $pkey === []) {
|
|
$array = null;
|
|
return;
|
|
} elseif (is_int($pkey) || (is_string($pkey) && strpos($pkey, ".") === false)) {
|
|
self::del($array, $pkey);
|
|
return;
|
|
} elseif (!is_array($pkey)) {
|
|
$pkey = explode(".", strval($pkey));
|
|
}
|
|
# pdel
|
|
A::ensure_array($array);
|
|
$current =& $array;
|
|
$key = null;
|
|
$last = count($pkey) - 1;
|
|
$i = 0;
|
|
foreach ($pkey as $key) {
|
|
if ($i == $last) break;
|
|
if ($current instanceof ArrayAccess) {
|
|
if (!$current->offsetExists($key)) break;
|
|
} elseif (is_array($current)) {
|
|
if (!array_key_exists($key, $current)) break;
|
|
} else {
|
|
break;
|
|
}
|
|
$current =& $current[$key];
|
|
$i++;
|
|
}
|
|
if ($i == $last) {
|
|
if ($current instanceof ArrayAccess) {
|
|
$current->offsetUnset($key);
|
|
} elseif (is_array($current)) {
|
|
unset($current[$key]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static final function each_pdel(&$array, ?array $pkeys): void {
|
|
if ($pkeys !== null) {
|
|
foreach ($pkeys as $pkey) {
|
|
self::pdel($array, $pkey);
|
|
}
|
|
}
|
|
}
|
|
|
|
#############################################################################
|
|
|
|
/**
|
|
* retourner le tableau $array en "renommant" les clés selon le tableau
|
|
* $mappings qui contient des associations de la forme [$from => $to]
|
|
*/
|
|
static function rekey(?array $array, ?array $mappings): ?array {
|
|
if ($array === null || $mappings === null) return $array;
|
|
$mapped = [];
|
|
foreach ($array as $key => $value) {
|
|
if (array_key_exists($key, $mappings)) $key = $mappings[$key];
|
|
$mapped[$key] = $value;
|
|
}
|
|
return $mapped;
|
|
}
|
|
|
|
#############################################################################
|
|
|
|
/** tester si tous les éléments du tableau satisfont la condition */
|
|
static final function all_if(?array $array, callable $cond): bool {
|
|
if ($array !== null) {
|
|
foreach ($array as $value) {
|
|
if (!$cond($value)) return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static final function all_z(?array $array): bool { return self::all_if($array, [cv::class, "z"]);}
|
|
static final function all_nz(?array $array): bool { return self::all_if($array, [cv::class, "nz"]);}
|
|
static final function all_n(?array $array): bool { return self::all_if($array, [cv::class, "n"]);}
|
|
static final function all_nn(?array $array): bool { return self::all_if($array, [cv::class, "nn"]);}
|
|
static final function all_t(?array $array): bool { return self::all_if($array, [cv::class, "t"]);}
|
|
static final function all_f(?array $array): bool { return self::all_if($array, [cv::class, "f"]);}
|
|
static final function all_pt(?array $array): bool { return self::all_if($array, [cv::class, "pt"]);}
|
|
static final function all_pf(?array $array): bool { return self::all_if($array, [cv::class, "pf"]);}
|
|
static final function all_equals(?array $array, $value): bool { return self::all_if($array, cv::equals($value)); }
|
|
static final function all_not_equals(?array $array, $value): bool { return self::all_if($array, cv::not_equals($value)); }
|
|
static final function all_same(?array $array, $value): bool { return self::all_if($array, cv::same($value)); }
|
|
static final function all_not_same(?array $array, $value): bool { return self::all_if($array, cv::not_same($value)); }
|
|
|
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
/** tester si au moins un élément du tableau satisfait la condition */
|
|
static final function any_if(?array $array, callable $cond): bool {
|
|
if ($array !== null) {
|
|
foreach ($array as $value) {
|
|
if ($cond($value)) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static final function any_z(?array $array): bool { return self::any_if($array, [cv::class, "z"]);}
|
|
static final function any_nz(?array $array): bool { return self::any_if($array, [cv::class, "nz"]);}
|
|
static final function any_n(?array $array): bool { return self::any_if($array, [cv::class, "n"]);}
|
|
static final function any_nn(?array $array): bool { return self::any_if($array, [cv::class, "nn"]);}
|
|
static final function any_t(?array $array): bool { return self::any_if($array, [cv::class, "t"]);}
|
|
static final function any_f(?array $array): bool { return self::any_if($array, [cv::class, "f"]);}
|
|
static final function any_pt(?array $array): bool { return self::any_if($array, [cv::class, "pt"]);}
|
|
static final function any_pf(?array $array): bool { return self::any_if($array, [cv::class, "pf"]);}
|
|
static final function any_equals(?array $array, $value): bool { return self::any_if($array, cv::equals($value)); }
|
|
static final function any_not_equals(?array $array, $value): bool { return self::any_if($array, cv::not_equals($value)); }
|
|
static final function any_same(?array $array, $value): bool { return self::any_if($array, cv::same($value)); }
|
|
static final function any_not_same(?array $array, $value): bool { return self::any_if($array, cv::not_same($value)); }
|
|
|
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
static final function filter_if(?array $array, callable $cond): ?array {
|
|
if ($array === null) return null;
|
|
$filtered = [];
|
|
$index = 0;
|
|
foreach ($array as $key => $value) {
|
|
if (!$cond($value)) {
|
|
if ($key === $index) {
|
|
$index++;
|
|
$filtered[] = $value;
|
|
} else {
|
|
$filtered[$key] = $value;
|
|
}
|
|
} elseif ($key === $index) {
|
|
$index++;
|
|
}
|
|
}
|
|
return $filtered;
|
|
}
|
|
|
|
static final function filter_z(?array $array): ?array { return self::filter_if($array, [cv::class, "z"]);}
|
|
static final function filter_nz(?array $array): ?array { return self::filter_if($array, [cv::class, "nz"]);}
|
|
static final function filter_n(?array $array): ?array { return self::filter_if($array, [cv::class, "n"]);}
|
|
static final function filter_nn(?array $array): ?array { return self::filter_if($array, [cv::class, "nn"]);}
|
|
static final function filter_t(?array $array): ?array { return self::filter_if($array, [cv::class, "t"]);}
|
|
static final function filter_f(?array $array): ?array { return self::filter_if($array, [cv::class, "f"]);}
|
|
static final function filter_pt(?array $array): ?array { return self::filter_if($array, [cv::class, "pt"]);}
|
|
static final function filter_pf(?array $array): ?array { return self::filter_if($array, [cv::class, "pf"]);}
|
|
static final function filter_equals(?array $array, $value): ?array { return self::filter_if($array, cv::equals($value)); }
|
|
static final function filter_not_equals(?array $array, $value): ?array { return self::filter_if($array, cv::not_equals($value)); }
|
|
static final function filter_same(?array $array, $value): ?array { return self::filter_if($array, cv::same($value)); }
|
|
static final function filter_not_same(?array $array, $value): ?array { return self::filter_if($array, cv::not_same($value)); }
|
|
|
|
#############################################################################
|
|
|
|
static final function sorted(?array $array, int $flags=SORT_REGULAR, bool $assoc=false): ?array {
|
|
if ($array === null) return null;
|
|
if ($assoc) asort($array, $flags);
|
|
else sort($array, $flags);
|
|
return $array;
|
|
}
|
|
|
|
static final function ksorted(?array $array, int $flags=SORT_REGULAR): ?array {
|
|
if ($array === null) return null;
|
|
ksort($array, $flags);
|
|
return $array;
|
|
}
|
|
|
|
/**
|
|
* retourner une fonction permettant de trier un tableau sur les clés
|
|
* spécifiées.
|
|
*
|
|
* - les clés ayant le préfixe '+' ou le suffixe '|asc' indiquent un tri
|
|
* ascendant
|
|
* - les clés ayant le préfixe '-' ou le suffixe '|desc' indiquent un tri
|
|
* descendant
|
|
* - sinon, par défaut, le tri est ascendant
|
|
*/
|
|
static final function compare(array $keys): callable {
|
|
return function ($a, $b) use ($keys) {
|
|
foreach ($keys as $key) {
|
|
if (str::del_prefix($key, "+")) $w = 1;
|
|
elseif (str::del_prefix($key, "-")) $w = -1;
|
|
elseif (str::del_suffix($key, "|asc")) $w = 1;
|
|
elseif (str::del_suffix($key, "|desc")) $w = -1;
|
|
else $w = 1;
|
|
if ($c = $w * cv::compare(cl::get($a, $key), cl::get($b, $key))) {
|
|
return $c;
|
|
}
|
|
}
|
|
return 0;
|
|
};
|
|
}
|
|
|
|
static final function usorted(?array $array, array $keys, bool $assoc=false): ?array {
|
|
if ($array === null) return null;
|
|
if ($assoc) uasort($array, self::compare($keys));
|
|
else usort($array, self::compare($keys));
|
|
return $array;
|
|
}
|
|
}
|