| @copyright 2007-2026 (c) AVE.cms | @link https://ave-cms.ru | @version 3.3 */ namespace App\Helpers; defined("BASEPATH") || die ('Direct access to this location is not allowed.'); use App\Common\Exceptions; use ArrayAccess; class Arr { /** * Проверить, является ли переменная массивом * * Метод проверяет, является ли переданное значение массивом или объектом, * который реализует интерфейс ArrayAccess. * * @param mixed $array Значение для проверки. * @return bool Возвращает TRUE, если значение - массив или ArrayAccess, иначе FALSE. * * @example * * $array = [1, 2, 3]; * $is_array = Arr::is($array); // true * * $not_array = 'hello'; * $is_array = Arr::is($not_array); // false * */ public static function is($array) : bool { return is_array($array) || $array instanceof ArrayAccess; } /** * Определяет, является ли массив ассоциативным. * * Массив считается ассоциативным, если его ключи не являются * последовательными целыми числами, начиная с 0. * * @param array $array Проверяемый массив. * @return bool Возвращает TRUE, если массив ассоциативный, иначе FALSE. * * @example * * Arr::isAssoc(['a' => 1, 'b' => 2]); // true * Arr::isAssoc([1, 2, 3]); // false * */ public static function isAssoc(array $array) { return (array_values($array) !== $array); } /** * Получить значение из массива по ключу. * * Метод позволяет получить значение из массива, в том числе вложенного, * используя "точечную" нотацию. Если ключ не найден, возвращается значение по умолчанию. * * @param \ArrayAccess|array $array Массив для поиска. * @param string|int|null $key Ключ для поиска. Может быть строкой или null. * @param mixed $default Значение, возвращаемое, если ключ не найден. * @param string|null $filter Фильтр для обработки полученного значения ('str', 'int', 'float', 'bool', 'trim'). * @return mixed Значение из массива или значение по умолчанию. * * @example * * $data = ['user' => ['name' => 'John', 'email' => 'john@example.com']]; * $name = Arr::get($data, 'user.name'); // 'John' * $role = Arr::get($data, 'user.role', 'guest'); // 'guest' * */ public static function get($array, $key, $default = null, $filter = null) { if (! self::is($array)) { return self::filter(self::resolveValue($default), $filter); } if (is_null($key)) { return self::filter($array, $filter); } if (self::exists($array, $key)) { return self::filter($array[$key], $filter); } if (strpos($key, '.') === false) { return self::filter($array[$key] ?? self::resolveValue($default), $filter); } foreach (explode('.', $key) as $segment) { if (self::is($array) && self::exists($array, $segment)) { $array = $array[$segment]; } else { return self::filter(self::resolveValue($default), $filter); } } return self::filter($array, $filter); } /** * Добавляет элемент в массив по ключу, если он не существует. * * Метод использует "точечную" нотацию для вложенных ключей. Если элемент * с таким ключом уже существует, массив не изменяется. * * @param array $array Массив, в который добавляется элемент. * @param string $key Ключ, по которому будет добавлен элемент. * @param mixed $value Значение для добавления. * @return array Возвращает измененный массив. * * @example * * $array = ['product' => ['name' => 'Desk']]; * Arr::add($array, 'product.price', 100); * // $array теперь: ['product' => ['name' => 'Desk', 'price' => 100]] * */ public static function add($array, $key, $value) { if (is_null(self::get($array, $key))) { self::set($array, $key, $value); } return $array; } /** * Устанавливает значение в массиве по ключу. * * Метод использует "точечную" нотацию для установки значения во вложенных массивах. * Если ключ равен null, будет заменен весь массив. * * @param array $array Массив, который изменяется (передается по ссылке). * @param string|null $key Ключ для установки значения. * @param mixed $value Новое значение. * @return array Возвращает измененный массив. * * @example * * $array = ['products' => ['desk' => ['price' => 100]]]; * Arr::set($array, 'products.desk.price', 200); * // $array теперь: ['products' => ['desk' => ['price' => 200]]] * */ public static function set(&$array, $key, $value) { if (is_null($key)) { return $array = $value; } $keys = explode('.', $key); while (count($keys) > 1) { $key = array_shift($keys); if (! isset($array[$key]) || ! is_array($array[$key])) { $array[$key] = []; } $array = &$array[$key]; } $array[array_shift($keys)] = $value; return $array; } /** * Проверяет существование ключа в массиве. * * Обертка для `array_key_exists`, которая также работает с объектами, * реализующими интерфейс `ArrayAccess`. * * @param \ArrayAccess|array $array Массив или объект для проверки. * @param string|int $key Ключ для поиска. * @return bool Возвращает TRUE, если ключ существует, иначе FALSE. */ public static function exists($array, $key) { if ($array instanceof ArrayAccess) { return $array->offsetExists($key); } return array_key_exists($key, $array); } /** * Проверяет наличие одного или нескольких ключей в массиве. * * Метод использует "точечную" нотацию для проверки существования * ключей во вложенных массивах. * * @param \ArrayAccess|array $array Массив для поиска. * @param string|array $keys Ключ или массив ключей. * @return bool Возвращает TRUE, если все указанные ключи существуют, иначе FALSE. * * @example * * $array = ['product' => ['name' => 'Desk', 'price' => 100]]; * Arr::has($array, 'product.name'); // true * Arr::has($array, ['product.name', 'product.price']); // true * Arr::has($array, 'product.discount'); // false * */ public static function has($array, $keys) { if (is_null($keys)) { return false; } $keys = (array) $keys; if (! $array) { return false; } if ($keys === []) { return false; } foreach ($keys as $key) { $subKeyArray = $array; if (self::exists($array, $key)) { continue; } foreach (explode('.', $key) as $segment) { if (self::is($subKeyArray) && self::exists($subKeyArray, $segment)) { $subKeyArray = $subKeyArray[$segment]; } else { return false; } } } return true; } /** * Возвращает первый элемент массива, удовлетворяющий условию. * * Если callback-функция не передана, возвращает просто первый элемент массива. * * @param array $array Массив для поиска. * @param callable|null $callback Условие в виде callback-функции. * @param mixed $default Значение по умолчанию, если элемент не найден. * @return mixed Найденный элемент или значение по умолчанию. * * @example * * $array = [100, 200, 300]; * $first = Arr::first($array, function ($value) { return $value >= 150; }); // 200 * */ public static function first($array, callable $callback = null, $default = null) { if (is_null($callback)) { if (empty($array)) { return self::resolveValue($default); } foreach ($array as $item) { return $item; } } foreach ($array as $key => $value) { if ($callback($value, $key)) { return $value; } } return self::resolveValue($default); } /** * Возвращает последний элемент массива, удовлетворяющий условию. * * Если callback-функция не передана, возвращает просто последний элемент массива. * * @param array $array Массив для поиска. * @param callable|null $callback Условие в виде callback-функции. * @param mixed $default Значение по умолчанию, если элемент не найден. * @return mixed Найденный элемент или значение по умолчанию. * * @example * * $array = [100, 200, 300, 400]; * $last = Arr::last($array, function ($value) { return $value < 350; }); // 300 * */ public static function last($array, callable $callback = null, $default = null) { if (is_null($callback)) { return empty($array) ? self::resolveValue($default) : end($array); } return self::first(array_reverse($array, true), $callback, $default); } /** * Преобразует многомерный массив в одномерный с "точечной" нотацией ключей. * * @param array $array Массив для преобразования. * @param string $prepend Префикс для новых ключей. * @return array Новый одномерный массив. * * @example * * $array = ['user' => ['name' => 'John', 'age' => 30]]; * $dotted = Arr::dot($array); * // ['user.name' => 'John', 'user.age' => 30] * */ public static function dot($array, $prepend = '') { $results = []; foreach ($array as $key => $value) { if (self::is($value) && ! empty($value)) { $results = array_merge($results, self::dot($value, $prepend . $key . '.')); } else { $results[$prepend . $key] = $value; } } return $results; } /** * Конвертирует массив в объект (stdClass). * * @param array $array Массив для конвертации. * @return \stdClass Конвертированный объект. */ public static function toObject($array) { if (is_array($array)) { $obj = new \StdClass(); foreach ($array as $key => $val) { $obj->$key = $val; } } else { $obj = $array; } return $obj; } /** * Рекурсивно конвертирует объект в массив. * * @param object|array $object Объект или массив для конвертации. * @return array Конвертированный массив. */ public static function toArray($object) { $object = (array)$object; if ($object === []) { return $object; } foreach ($object as $key => &$value) { if ((is_object($value) || is_array($value))) { $object[$key] = self::toArray($value); } } return $object; } /** * Конвертирует объект (в т.ч. SimpleXMLElement) в массив. * * Этот метод использует `json_encode`, а затем `json_decode` для преобразования. * * @param object $object Объект для конвертации. * @return array Конвертированный массив. */ public static function objectToArray($object) { if (is_object($object)) { $object = json_decode(json_encode($object), true, 512); } return $object; } /** * Сортирует многомерный массив по одному или нескольким ключам. * * @param array $array Массив для сортировки. * @param string|array $key Ключ или массив ключей для сортировки. * @param int $sort_flags Флаги сортировки PHP (например, SORT_REGULAR, SORT_NUMERIC). * @param int $sort_way Направление сортировки (SORT_ASC или SORT_DESC). * @return array Отсортированный массив. */ public static function multiSort($array, $key, $sort_flags = SORT_REGULAR, $sort_way = SORT_ASC) { if (! self::is($array) || count($array) === 0 || empty($key)) { return $array; } $dir = ($sort_way === SORT_DESC) ? -1 : 1; if (! self::is($key)) { // Одиночный ключ — asort с поддержкой sort_flags $mapping = []; foreach ($array as $k => $v) { $mapping[$k] = $v[$key]; } if ($sort_way === SORT_DESC) { arsort($mapping, $sort_flags); } else { asort($mapping, $sort_flags); } $sorted = []; foreach ($mapping as $k => $v) { $sorted[] = $array[$k]; } return $sorted; } // Множественные ключи — последовательная сортировка через usort $keys = $key; usort($array, function ($a, $b) use ($keys, $dir) { foreach ($keys as $k) { $av = isset($a[$k]) ? $a[$k] : null; $bv = isset($b[$k]) ? $b[$k] : null; $cmp = $av <=> $bv; if ($cmp !== 0) { return $cmp * $dir; } } return 0; }); return $array; } /** * Безопасно сериализует массив. * * Преобразует массив в строку и кодирует в Base64. * * @param array $array Массив для сериализации. * @return string Сериализованная и закодированная в Base64 строка. */ public static function safeSerialize(array $array) { return base64_encode(serialize($array)); } /** * Безопасно десериализует строку в массив. * * Декодирует строку из Base64 и выполняет десериализацию, * запрещая создание объектов. * * @param string $string Строка для десериализации. * @return array|false Десериализованный массив или false в случае ошибки. */ public static function safeUnserialize(string $string) { return unserialize(base64_decode($string), ['allowed_classes' => false]); } /** * Рекурсивно удаляет экранирующие слэши. * * Аналог `stripslashes`, но для многомерных массивов. * * @param array|string $array Массив или строка для обработки. * @return array|string Обработанный массив или строка без слэшей. */ public static function stripslashesArray($array) { if (self::is($array)) { return array_map([__CLASS__, 'stripslashesArray'], $array); } return stripslashes($array); } /** * Ищет элементы в массиве по заданному ключу и значению. * * @param array $array Массив, в котором ведется поиск. * @param mixed $key Ключ для сравнения. * @param mixed $value Значение для сравнения. * @return array|int Возвращает массив найденных элементов или 0, если ничего не найдено. */ public static function find(array $array, $key, $value) { $result = []; if (self::is($array)) { foreach ($array as $val) { if ((is_object($val) ? ($val->$key == $value) : ($val[$key] == $value))) { $result[] = $val; } } } return (! empty($result)) ? $result : 0; } /** * Ищет ключ элемента в массиве по значению другого ключа. * * @param mixed $value Значение для поиска. * @param mixed $array Массив для поиска. * @return bool|int|string Ключ найденного элемента или false, если не найдено. */ public static function searchForValue($value, $array) { if (is_array($array)) { return array_search($value, $array, true); } return false; } /** * Ищет значение в массиве по ключу и значению другого поля. * * @param mixed $key Ключ для поиска. * @param mixed $value Значение для поиска. * @param mixed $return Имя ключа, значение которого нужно вернуть. * @param \ArrayAccess|array $array Массив для поиска. * @param bool $fullkey Если true, возвращает весь найденный элемент (массив/объект). * @return bool|mixed Найденное значение, элемент или false. */ public static function searchForValueName($key, $value, $return, $array, bool $fullkey = false) { if (self::is($array)) { foreach ($array as $k => $val) { if (is_object($array)) { if ($val->$key === $value) { return $fullkey ? $val : $val->$return; } } elseif ($val[$key] === $value) { return $fullkey ? $val : $val[$return]; } } } return false; } /** * Подсчитывает количество элементов в массиве по ключу и значению. * * @param array $array Массив. * @param string $key Ключ для проверки. * @param string $value Значение для сравнения. * @return int Количество совпадений. */ public static function countInArray(array $array, string $key, string $value) { $i = 0; if (self::is($array)) { foreach ($array as $k => $v) { if ((is_object($v) ? ($v->$key == $value) : ($v[$key] == $value))) { $i++; } } } return $i; } /** * Сортирует массив по указанному полю или полям. * * @param array $data Массив данных для сортировки. * @param string|array $field Поле или массив полей для сортировки. * @return array Отсортированный массив с числовыми ключами. * * @example * * $data = [['name' => 'John'], ['name' => 'Alex']]; * $sorted = Arr::sortArray($data, 'name'); * // $sorted[0] будет ['name' => 'Alex'] * */ public static function sortArray($data, $field) { $field = (array)$field; uasort($data, static function ($a, $b) use ($field) { $retval = 0; foreach ($field as $fieldname) { if ($retval == 0) { $retval = strnatcmp($a[$fieldname], $b[$fieldname]); } } return $retval; }); return array_values($data); } /** * Сериализует массив. * * @param array $array Массив. * @return string|false Сериализованная строка или false в случае ошибки. */ public static function arrayToSerial(array $array) { if (self::is($array)) { return serialize($array); } return false; } /** * Десериализует строку в массив. * * @param string $string Сериализованная строка. * @return mixed|false Массив или false в случае ошибки. */ public static function unserialToArray(string $string) { if ($string) { return unserialize($string, ['allowed_classes' => false]); } return false; } /** * Применяет фильтр к значению для его типизации. * * @param mixed $value Значение для фильтрации. * @param string|null $filter Тип фильтра ('str', 'int', 'float', 'bool', 'trim'). * @return mixed Отфильтрованное значение. * @throws Exceptions Если указан неподдерживаемый тип фильтра. */ protected static function filter($value, $filter) { if (! is_null($filter)) { switch ($filter) { case 'str': case 'string': case 'strval': $value = is_scalar($value) ? (string)$value : ''; break; case 'trim': $value = is_scalar($value) ? trim($value) : ''; break; case 'int': case 'integer': case 'intval': $value = is_scalar($value) ? (int)$value : 0; break; case 'float': case 'floatval': $value = is_scalar($value) ? (float)$value : 0.0; break; case 'bool': case 'boolean': case 'boolval': $value = is_scalar($value) && (bool)$value; break; default: throw new Exceptions('Arr wrong \'%name\' filter name', ['%name' => $filter]); } } return $value; } /** * Вычислить отложенное значение без зависимости от глобальных helpers. * * @param mixed $value * @return mixed */ protected static function resolveValue($value) { return $value instanceof \Closure ? $value() : $value; } }