Files
wiosna-dev_common-library/src/Utilities/Arrays.php
2019-03-24 22:19:45 +01:00

1791 lines
51 KiB
PHP

<?php
/**
* (c) Meritoo.pl, http://www.meritoo.pl
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Meritoo\Common\Utilities;
/**
* Useful methods related to arrays
*
* @author Meritoo <github@meritoo.pl>
* @copyright Meritoo <http://www.meritoo.pl>
*/
class Arrays
{
/**
* Name of the array's key used to store position of element of the array
*
* @var string
*/
const POSITION_KEY_NAME = 'position';
/**
* Converts given array's column to string.
* Recursive call is made for multi-dimensional arrays.
*
* @param array $array Data to be converted
* @param string|int $arrayColumnKey (optional) Column name. Default: "".
* @param string $separator (optional) Separator used between values. Default: ",".
* @return string|null
*/
public static function values2string(array $array, $arrayColumnKey = '', $separator = ',')
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$values = [];
foreach ($array as $key => $value) {
$appendMe = null;
if (is_array($value)) {
$appendMe = self::values2string($value, $arrayColumnKey, $separator);
} elseif (empty($arrayColumnKey)) {
$appendMe = $value;
} elseif ($key === $arrayColumnKey) {
$appendMe = $array[$arrayColumnKey];
}
/*
* Part to append is unknown?
* Let's go to next part
*/
if (null === $appendMe) {
continue;
}
$values[] = $appendMe;
}
/*
* No values found?
* Nothing to do
*/
if (empty($values)) {
return null;
}
return implode($separator, $values);
}
/**
* Converts given array to string with keys, e.g. abc=1&def=2 or abc="1" def="2"
*
* @param array $array Data to be converted
* @param string $separator (optional) Separator used between name-value pairs. Default: ",".
* @param string $valuesKeysSeparator (optional) Separator used between name and value. Default: "=".
* @param string $valuesWrapper (optional) Wrapper used to wrap values, e.g. double-quote: key="value".
* Default: "".
* @return string|null
*/
public static function valuesKeys2string(
array $array,
$separator = ',',
$valuesKeysSeparator = '=',
$valuesWrapper = ''
) {
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$result = '';
foreach ($array as $key => $value) {
if (!empty($result)) {
$result .= $separator;
}
if (!empty($valuesWrapper)) {
$value = sprintf('%s%s%s', $valuesWrapper, $value, $valuesWrapper);
}
$result .= $key . $valuesKeysSeparator . $value;
}
return $result;
}
/**
* Converts given array's rows to csv string
*
* @param array $array Data to be converted. It have to be an array that represents database table.
* @param string $separator (optional) Separator used between values. Default: ",".
* @return string|null
*/
public static function values2csv(array $array, $separator = ',')
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$rows = [];
$lineSeparator = "\n";
foreach ($array as $row) {
/*
* I have to use html_entity_decode() function here, because some string values can contain
* entities with semicolon and this can destroy the CSV column order.
*/
if (is_array($row) && !empty($row)) {
foreach ($row as $key => $value) {
$row[$key] = html_entity_decode($value);
}
$rows[] = implode($separator, $row);
}
}
if (empty($rows)) {
return '';
}
return implode($lineSeparator, $rows);
}
/**
* Returns information if given element is the first one
*
* @param array $array The array to get the first element of
* @param mixed $element The element to check / verify
* @param bool $firstLevelOnly (optional) If is set to true, first element is returned. Otherwise - totally
* first element is returned (first of the First array).
* @return bool
*/
public static function isFirstElement(array $array, $element, $firstLevelOnly = true)
{
$firstElement = self::getFirstElement($array, $firstLevelOnly);
return $element === $firstElement;
}
/**
* Returns the first element of given array
*
* It may be first element of given array or the totally first element from the all elements (first element of the
* first array).
*
* @param array $array The array to get the first element of
* @param bool $firstLevelOnly (optional) If is set to true, first element is returned. Otherwise - totally
* first element is returned (first of the first array).
* @return mixed
*/
public static function getFirstElement(array $array, $firstLevelOnly = true)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$firstKey = self::getFirstKey($array);
$first = $array[$firstKey];
if (!$firstLevelOnly && is_array($first)) {
$first = self::getFirstElement($first, $firstLevelOnly);
}
return $first;
}
/**
* Returns first key of array
*
* @param array $array The array to get the first key of
* @return mixed
*/
public static function getFirstKey(array $array)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$keys = array_keys($array);
return $keys[0];
}
/**
* Returns information if given element is the last one
*
* @param array $array The array to get the last element of
* @param mixed $element The element to check / verify
* @param bool $firstLevelOnly (optional) If is set to true, last element is returned. Otherwise - totally
* last element is returned (last of the latest array).
* @return bool
*/
public static function isLastElement(array $array, $element, $firstLevelOnly = true)
{
$lastElement = self::getLastElement($array, $firstLevelOnly);
return $element === $lastElement;
}
/**
* Returns the last element of given array
*
* It may be last element of given array or the totally last element from the all elements (last element of the
* latest array).
*
* @param array $array The array to get the last element of
* @param bool $firstLevelOnly (optional) If is set to true, last element is returned. Otherwise - totally
* last element is returned (last of the latest array).
* @return mixed
*/
public static function getLastElement(array $array, $firstLevelOnly = true)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$last = end($array);
if (!$firstLevelOnly && is_array($last)) {
$last = self::getLastElement($last, $firstLevelOnly);
}
return $last;
}
/**
* Returns breadcrumb (a path) to the last element of array
*
* @param array $array Data to get the breadcrumb
* @param string $separator (optional) Separator used to stick the elements. Default: "/".
* @return string|null
*/
public static function getLastElementBreadCrumb(array $array, $separator = '/')
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$keys = array_keys($array);
$keysCount = count($keys);
$lastKey = $keys[$keysCount - 1];
$last = end($array);
$breadCrumb = $lastKey;
if (is_array($last)) {
$crumb = self::getLastElementBreadCrumb($last, $separator);
} else {
$crumb = $last;
}
return $breadCrumb . $separator . $crumb;
}
/**
* Returns the last row of array
*
* @param array $array The array to get the last row of
* @return mixed
*/
public static function getLastRow(array $array)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$effect = [];
$last = end($array);
if (is_array($last)) {
/*
* We've got an array, so looking for the last row of array will be done recursively
*/
$effect = self::getLastRow($last);
/*
* The last row is not an array or it's an empty array?
* Let's use the previous candidate
*/
if (!is_array($effect) || (is_array($effect) && empty($effect))) {
$effect = $last;
}
}
return $effect;
}
/**
* Replaces array keys that match given pattern with new key name
*
* @param array $dataArray The array
* @param string $oldKeyPattern Old key pattern
* @param string $newKey New key name
* @return array
*/
public static function replaceArrayKeys($dataArray, $oldKeyPattern, $newKey)
{
$effect = [];
if (is_array($dataArray) && !empty($dataArray)) {
foreach ($dataArray as $key => $value) {
if (preg_match($oldKeyPattern, $key)) {
$key = $newKey;
}
if (is_array($value)) {
$value = self::replaceArrayKeys($value, $oldKeyPattern, $newKey);
}
$effect[$key] = $value;
}
}
return $effect;
}
/**
* Generates JavaScript code for given PHP array
*
* @param array $array The array that should be generated to JavaScript
* @param string $jsVariableName (optional) Name of the variable that will be in generated JavaScript code
* @param bool $preserveIndexes (optional) If is set to true and $jsVariableName isn't empty, indexes also
* will be added to the JavaScript code. Otherwise not.
* @return string|null
*/
public static function array2JavaScript(array $array, $jsVariableName = '', $preserveIndexes = false)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$result = '';
$counter = 0;
$arrayCount = count($array);
$arrayPrepared = self::quoteStrings($array);
$isMultiDimensional = self::isMultiDimensional($arrayPrepared);
/*
* Name of the variable was not provided and it's a multi dimensional array?
* Let's create the name, because variable is required for later usage (related to multi dimensional array)
*/
if (empty($jsVariableName) && $isMultiDimensional) {
$jsVariableName = 'autoGeneratedVariable';
}
if (!empty($jsVariableName) && is_string($jsVariableName)) {
$result .= sprintf('var %s = ', $jsVariableName);
}
$result .= 'new Array(';
if ($preserveIndexes || $isMultiDimensional) {
$result .= $arrayCount;
$result .= ');';
}
foreach ($arrayPrepared as $index => $value) {
++$counter;
if (is_array($value)) {
$variable = $index;
if (is_int($index)) {
$variable = 'value_' . $variable;
}
$value = self::array2JavaScript($value, $variable, $preserveIndexes);
if (null !== $value && '' !== $value) {
/*
* Add an empty line for the 1st iteration only. Required to avoid missing empty line after
* declaration of variable:
*
* var autoGeneratedVariable = new Array(...);autoGeneratedVariable[0] = new Array(...);
* autoGeneratedVariable[1] = new Array(...);
*/
if (1 === $counter) {
$result .= "\n";
}
$result .= $value . "\n";
$result .= sprintf('%s[%s] = %s;', $jsVariableName, Miscellaneous::quoteValue($index), $variable);
if ($counter !== $arrayCount) {
$result .= "\n";
}
}
} elseif ($preserveIndexes) {
if (!empty($jsVariableName)) {
$index = Miscellaneous::quoteValue($index);
$result .= sprintf("\n%s[%s] = %s;", $jsVariableName, $index, $value);
}
} else {
$format = '%s';
if ($counter < $arrayCount) {
$format .= ', ';
}
$result .= sprintf($format, $value);
}
}
if (!$preserveIndexes && !$isMultiDimensional) {
$result .= ');';
}
return $result;
}
/**
* Quotes (adds quotes) to elements that are strings and returns new array (with quoted elements)
*
* @param array $array The array to check for string values
* @return array|null
*/
public static function quoteStrings(array $array)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$result = [];
foreach ($array as $index => $value) {
if (is_array($value)) {
$value = self::quoteStrings($value);
} elseif (is_string($value)) {
if (!Regex::isQuoted($value)) {
$value = '\'' . $value . '\'';
}
}
$result[$index] = $value;
}
return $result;
}
/**
* Removes marginal element (first or last)
*
* @param string|array $item The item which should be shortened
* @param bool $last (optional) If is set to true, last element is removed. Otherwise - first.
* @return string|array
*/
public static function removeMarginalElement($item, $last = true)
{
if (is_string($item)) {
if ($last) {
$item = substr($item, 0, -1);
} else {
$item = substr($item, 1);
}
} elseif (is_array($item)) {
$key = self::getFirstKey($item);
if ($last) {
$key = self::getLastKey($item);
}
unset($item[$key]);
}
return $item;
}
/**
* Returns last key of array
*
* @param array $array The array to get the last key of
* @return mixed
*/
public static function getLastKey(array $array)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$keys = array_keys($array);
return end($keys);
}
/**
* Removes element / item of given array
*
* @param array $array The array that contains element / item which should be removed
* @param mixed $item The element / item which should be removed
* @return bool|array
*/
public static function removeElement(array $array, $item)
{
/*
* No elements or the element does not exist?
* Nothing to do
*/
if (empty($array) || !in_array($item, $array, true)) {
return false;
}
/*
* Flip the array to make it looks like: value => key
*/
$arrayFlipped = array_flip($array);
/*
* Take the key of element / item that should be removed
*/
$key = $arrayFlipped[$item];
/*
* ...and remove the element / item
*/
unset($array[$key]);
return $array;
}
/**
* Removes items from given array starting at given element (before or after the element)
*
* @param array $array The array which contains items to remove
* @param mixed $needle The element which is start point of deletion
* @param bool $before (optional) If is set to true, all elements before given needle are removed. Otherwise - all
* after needle.
*/
public static function removeElements(array &$array, $needle, $before = true)
{
if (!empty($array)) {
if (!$before) {
$array = array_reverse($array, true);
}
foreach ($array as $key => &$value) {
$remove = false;
$isArray = is_array($value);
if ($isArray) {
self::removeElements($value, $needle, $before);
if ($isArray && empty($value)) {
$remove = true;
}
} elseif ($value === $needle) {
break;
} else {
$remove = true;
}
if ($remove) {
unset($array[$key]);
}
}
if (!$before) {
$array = array_reverse($array, true);
}
}
}
/**
* Sets keys as values and values as keys in given array.
* Replaces keys with values.
*
* @param array $array The array to change values with keys
* @param bool $ignoreDuplicatedValues (optional) If is set to true, duplicated values are ignored. This means that
* when there is more than 1 value and that values become key, only the last
* value will be used with it's key, because other will be overridden.
* Otherwise - values are preserved and keys assigned to that values are
* returned as an array.
* @return array|null
*
* Example of $ignoreDuplicatedValues = false:
* - provided array
* $array = [
* 'lorem' => 100, // <-- Duplicated value
* 'ipsum' => 200,
* 'dolor' => 100, // <-- Duplicated value
* ];
*
* - result
* $replaced = [
* 100 => [
* 'lorem', // <-- Key of duplicated value
* 'dolor', // <-- Key of duplicated value
* ],
* 200 => 'ipsum',
* ];
*/
public static function setKeysAsValues(array $array, $ignoreDuplicatedValues = true)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$replaced = [];
foreach ($array as $key => $value) {
/*
* The value it's an array?
* Let's replace keys with values in this array first
*/
if (is_array($value)) {
$replaced[$key] = self::setKeysAsValues($value, $ignoreDuplicatedValues);
continue;
}
/*
* Duplicated values shouldn't be ignored and processed value is used as key already?
* Let's use an array and that will contain all values (to avoid ignoring / overriding duplicated values)
*/
if (!$ignoreDuplicatedValues && isset($replaced[$value])) {
$existing = self::makeArray($replaced[$value]);
$replaced[$value] = array_merge($existing, [
$key,
]);
continue;
}
/*
* Standard behaviour
*/
$replaced[$value] = $key;
}
return $replaced;
}
/**
* Applies ksort() function recursively in the given array
*
* @param array $array The array to sort
* @param int $sortFlags (optional) Options of ksort() function
* @return array|null
*/
public static function ksortRecursive(array &$array, $sortFlags = SORT_REGULAR)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$effect = &$array;
ksort($effect, $sortFlags);
foreach ($effect as &$value) {
if (is_array($value)) {
ksort($value, $sortFlags);
}
}
return $effect;
}
/**
* Returns count / amount of elements that are not array
*
* @param array $array The array to count
* @return int|null
*/
public static function getNonArrayElementsCount(array $array)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$count = 0;
foreach ($array as &$value) {
if (is_array($value)) {
$count += self::getNonArrayElementsCount($value);
continue;
}
++$count;
}
return $count;
}
/**
* Converts given string with special separators to array
*
* Example:
* ~ string:
* "light:jasny|dark:ciemny"
*
* ~ array as a result:
* [
* 'light' => 'jasny',
* 'dark' => 'ciemny',
* ]
*
* @param string $string The string to be converted
* @param string $separator (optional) Separator used between name-value pairs in the string.
* Default: "|".
* @param string $valuesKeysSeparator (optional) Separator used between name and value in the string. Default: ":".
* @return array
*/
public static function string2array($string, $separator = '|', $valuesKeysSeparator = ':')
{
/*
* Empty string?
* Nothing to do
*/
if (empty($string)) {
return null;
}
$array = [];
$exploded = explode($separator, $string);
foreach ($exploded as $item) {
$exploded2 = explode($valuesKeysSeparator, $item);
if (2 === count($exploded2)) {
$key = trim($exploded2[0]);
$value = trim($exploded2[1]);
$array[$key] = $value;
}
}
return $array;
}
/**
* Returns information if given keys exist in given array
*
* @param array $keys The keys to find
* @param array $array The array that maybe contains keys
* @param bool $explicit (optional) If is set to true, all keys should exist in given array. Otherwise - not all.
* @return bool
*/
public static function areKeysInArray(array $keys, array $array, $explicit = true)
{
$result = false;
if (!empty($array)) {
$firstKey = true;
foreach ($keys as $key) {
$exists = array_key_exists($key, $array);
if ($firstKey) {
$result = $exists;
$firstKey = false;
} elseif ($explicit) {
$result = $result && $exists;
if (!$result) {
break;
}
} else {
$result = $result || $exists;
if ($result) {
break;
}
}
}
}
return $result;
}
/**
* Returns paths of the last elements
*
* @param array $array The array with elements
* @param string $separator (optional) Separator used between elements. Default: ".".
* @param string $parentPath (optional) Path of the parent element. Default: "".
* @param string|array $stopIfMatchedBy (optional) Patterns of keys or paths that matched will stop the process
* of path building and including children of those keys or paths (recursive
* will not be used for keys in lower level of given array). Default: "".
* @return array|null
*
* Examples - $stopIfMatchedBy argument:
* a) "\d+"
* b) [
* "lorem\-",
* "\d+",
* ];
*/
public static function getLastElementsPaths(array $array, $separator = '.', $parentPath = '', $stopIfMatchedBy = '')
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
if (!empty($stopIfMatchedBy)) {
$stopIfMatchedBy = self::makeArray($stopIfMatchedBy);
}
$paths = [];
foreach ($array as $key => $value) {
$path = $key;
$stopRecursion = false;
$valueIsArray = is_array($value);
/*
* If the path of parent element is delivered,
* I have to use it and build longer path
*/
if (!empty($parentPath)) {
$pathTemplate = '%s%s%s';
$path = sprintf($pathTemplate, $parentPath, $separator, $key);
}
/*
* Check if the key or current path matches one of patterns at which the process should be stopped,
* the recursive not used. It means that I have to pass current value and stop processing of the
* array (don't go to the next step).
*/
if (!empty($stopIfMatchedBy)) {
foreach ($stopIfMatchedBy as $rawPattern) {
$pattern = sprintf('|%s|', $rawPattern);
if (preg_match($pattern, $key) || preg_match($pattern, $path)) {
$stopRecursion = true;
break;
}
}
}
/*
* The value is passed to the returned array if:
* - it's not an array
* or
* - the process is stopped, recursive is not used
*/
if (!$valueIsArray || ($valueIsArray && empty($value)) || $stopRecursion) {
$paths[$path] = $value;
continue;
}
/*
* Let's iterate through the next level, using recursive
*/
if ($valueIsArray) {
$recursivePaths = self::getLastElementsPaths($value, $separator, $path, $stopIfMatchedBy);
$paths += $recursivePaths;
}
}
return $paths;
}
/**
* Makes and returns an array for given variable
*
* @param mixed $variable Variable that should be an array
* @return array
*/
public static function makeArray($variable)
{
if (is_array($variable)) {
return $variable;
}
return [$variable];
}
/**
* Returns information if keys / indexes of given array are matched by given pattern
*
* @param array $array The array to check
* @param string $pattern The pattern which keys / indexes should match, e.g. "\d+"
* @param bool $firstLevelOnly (optional) If is set to true, all keys / indexes are checked. Otherwise - from the
* first level only.
* @return bool
*/
public static function areAllKeysMatchedByPattern(array $array, $pattern, $firstLevelOnly = false)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return false;
}
/*
* I suppose that all are keys are matched
* and then I have to look for keys that don't matches
*/
$areMatched = true;
/*
* Building the pattern
*/
$rawPattern = $pattern;
$pattern = sprintf('|%s|', $rawPattern);
foreach ($array as $key => $value) {
/*
* Not matched? So I have to stop the iteration, because one not matched key
* means that not all keys are matched by given pattern
*/
if (!preg_match($pattern, $key)) {
$areMatched = false;
break;
}
/*
* The not matching key was not found and the value is an array?
* Let's begin recursive looking for result
*/
if ($areMatched && is_array($value) && !$firstLevelOnly) {
$areMatched = self::areAllKeysMatchedByPattern($value, $rawPattern, $firstLevelOnly);
}
}
return $areMatched;
}
/**
* Returns information if keys / indexes of given array are integers, in other words if the array contains
* zero-based keys / indexes
*
* @param array $array The array to check
* @param bool $firstLevelOnly (optional) If is set to true, all keys / indexes are checked. Otherwise - from the
* first level only (default behaviour).
* @return bool
*/
public static function areAllKeysIntegers(array $array, $firstLevelOnly = false)
{
$pattern = '\d+';
return self::areAllKeysMatchedByPattern($array, $pattern, $firstLevelOnly);
}
/**
* Returns value of given array set under given path of keys, of course if the value exists.
* The keys should be delivered in the same order as used by source array.
*
* @param array $array The array which should contains a value
* @param array $keys Keys, path of keys, to find in given array
* @return mixed
*
* Examples:
* a) $array
* [
* 'some key' => [
* 'another some key' => [
* 'yet another key' => 123,
* ],
* 'some different key' => 456,
* ]
* ]
*
* b) $keys
* [
* 'some key',
* 'another some key',
* 'yet another key',
* ]
*
* Based on the above examples will return:
* 123
*/
public static function getValueByKeysPath(array $array, array $keys)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$value = null;
if (self::issetRecursive($array, $keys)) {
foreach ($keys as $key) {
$value = $array[$key];
array_shift($keys);
if (is_array($value) && !empty($keys)) {
$value = self::getValueByKeysPath($value, $keys);
}
break;
}
}
return $value;
}
/**
* Returns information if given path of keys are set is given array.
* The keys should be delivered in the same order as used by source array.
*
* @param array $array The array to check
* @param array $keys Keys, path of keys, to find in given array
* @return bool
*
* Examples:
* a) $array
* [
* 'some key' => [
* 'another some key' => [
* 'yet another key' => 123,
* ],
* 'some different key' => 456,
* ]
* ]
*
* b) $keys
* [
* 'some key',
* 'another some key',
* 'yet another key',
* ]
*/
public static function issetRecursive(array $array, array $keys)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return false;
}
$isset = false;
foreach ($keys as $key) {
$isset = isset($array[$key]);
if ($isset) {
$newArray = $array[$key];
array_shift($keys);
if (is_array($newArray) && !empty($keys)) {
$isset = self::issetRecursive($newArray, $keys);
}
}
break;
}
return $isset;
}
/**
* Returns all values of given key.
* It may be useful when you want to retrieve all values of one column.
*
* @param array $array The array which should contain values of the key
* @param string $key The key
* @return array|null
*/
public static function getAllValuesOfKey(array $array, $key)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$values = [];
foreach ($array as $index => $value) {
if ($index === $key) {
$values[] = $value;
continue;
}
if (is_array($value)) {
$recursiveValues = self::getAllValuesOfKey($value, $key);
if (!empty($recursiveValues)) {
$merged = array_merge($values, $recursiveValues);
$values = $merged;
}
}
}
return $values;
}
/**
* Sets positions for each element / child of given array and returns the array
*
* Position for the 1st element / child of a parent is set to 1 and incremented for the next element and
* so on. Each parent is treated as separate array, so its elements are treated as positioned at 1st level.
*
* @param array $array The array which should has values of position for each element
* @param string $keyName (optional) Name of key which will contain the position value
* @param int $startPosition (optional) Default, start value of the position for main / given array, not the
* children
* @return array|null
*/
public static function setPositions(array $array, $keyName = self::POSITION_KEY_NAME, $startPosition = null)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$childPosition = 1;
if (null !== $startPosition) {
$array[$keyName] = $startPosition;
}
foreach ($array as &$value) {
if (is_array($value)) {
$value = self::setPositions($value, $keyName, $childPosition);
++$childPosition;
}
}
return $array;
}
/**
* Trims string values of given array and returns the new array
*
* @param array $array The array which values should be trimmed
* @return array
*/
public static function trimRecursive(array $array)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return [];
}
$result = [];
foreach ($array as $key => $value) {
if (is_array($value)) {
$result[$key] = self::trimRecursive($value);
continue;
}
if (is_string($value)) {
$value = trim($value);
}
$result[$key] = $value;
}
return $result;
}
/**
* Sorts an array by keys given in second array as values.
* Keys which are not in array with order are pushed after sorted elements.
*
* Example:
* - array to sort:
* <code>
* array(
* 'lorem' => array(
* 'ipsum'
* ),
* 'dolor' => array(
* 'sit',
* 'amet'
* ),
* 'neque' => 'neque'
* )
* </code>
* - keys order:
* <code>
* array(
* 'dolor',
* 'lorem'
* )
* </code>
* - the result:
* <code>
* array(
* 'dolor' => array(
* 'sit',
* 'amet'
* ),
* 'lorem' => array(
* 'ipsum'
* ),
* 'neque' => 'neque' // <-- the rest, values of other keys
* )
* </code>
*
* @param array $array An array to sort
* @param array $keysOrder An array with keys of the 1st argument in proper / required order
* @return array|null
*/
public static function sortByCustomKeysOrder(array $array, array $keysOrder)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$ordered = [];
/*
* 1st iteration:
* Get elements in proper / required order
*/
if (!empty($keysOrder)) {
foreach ($keysOrder as $key) {
if (isset($array[$key])) {
$ordered[$key] = $array[$key];
unset($array[$key]);
}
}
}
/*
* 2nd iteration:
* Get the rest of elements
*/
if (!empty($array)) {
foreach ($array as $key => $element) {
$ordered[$key] = $element;
}
}
return $ordered;
}
/**
* Returns smartly imploded string
*
* Separators located at the beginning or end of elements are removed.
* It's required to avoid problems with duplicated separator, e.g. "first//second/third", where separator is a
* "/" string.
*
* @param array $array The array with elements to implode
* @param string $separator Separator used to stick together elements of given array
* @return string|null
*/
public static function implodeSmart(array $array, $separator)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
foreach ($array as &$element) {
if (is_array($element)) {
$element = self::implodeSmart($element, $separator);
}
if (Regex::startsWith($element, $separator)) {
$element = substr($element, 1);
}
if (Regex::endsWith($element, $separator)) {
$element = substr($element, 0, -1);
}
}
return implode($separator, $array);
}
/**
* Returns information if given array is empty, iow. information if all elements of given array are empty
*
* @param array $array The array to verify
* @param bool $strictNull (optional) If is set to true elements are verified if they are null. Otherwise - only
* if they are empty (e.g. null, '', 0, array()).
* @return bool
*/
public static function areAllValuesEmpty(array $array, $strictNull = false)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return false;
}
foreach ($array as $element) {
/*
* If elements are verified if they are exactly null and the element is:
* - not an array
* - not null
* or elements are NOT verified if they are exactly null and the element is:
* - not empty (e.g. null, '', 0, array())
*
* If one of the above is true, not all elements of given array are empty
*/
if ((!is_array($element) && $strictNull && null !== $element) || !empty($element)) {
return false;
}
}
return true;
}
/**
* Returns an array containing all the entries from 1st array that are not present in 2nd array.
* An item from 1st array is the same as in 2nd array if both, keys and values, are the same.
*
* Example of difference:
* $array1 = [
* 1 => 'Lorem',
* 2 => 'ipsum,
* ];
*
* $array2 = [
* 1 => 'Lorem',
* 5 => 'ipsum, // <-- The same values, but different key. Here we got 5, in 1st array - 2.
* ];
*
* @param array $array1 The 1st array to verify
* @param array $array2 The 2nd array to verify
* @param bool $valuesOnly (optional) If is set to true, compares values only. Otherwise - keys and values
* (default behaviour).
* @return array
*/
public static function arrayDiffRecursive(array $array1, array $array2, $valuesOnly = false)
{
$effect = [];
/*
* Values should be compared only and both arrays are one-dimensional?
* Let's find difference by using simple function
*/
if ($valuesOnly && 1 === self::getDimensionsCount($array1) && 1 === self::getDimensionsCount($array2)) {
return array_diff($array1, $array2);
}
foreach ($array1 as $key => $value) {
$array2HasKey = array_key_exists($key, $array2);
/*
* Values should be compared only?
*/
if ($valuesOnly) {
$difference = null;
if (is_array($value)) {
if ($array2HasKey && is_array($array2[$key])) {
$difference = self::arrayDiffRecursive($value, $array2[$key], $valuesOnly);
}
} elseif (!$array2HasKey || ($array2HasKey && $value !== $array2[$key])) {
/*
* We are here, because:
* a) 2nd array hasn't key from 1st array
* OR
* b) key exists in both, 1st and 2nd array, but values are different
*/
$difference = $value;
}
if (null !== $difference) {
$effect[] = $difference;
}
/*
* The key exists in 2nd array?
*/
} elseif ($array2HasKey) {
/*
* The value it's an array (it's a nested array)?
*/
if (is_array($value)) {
$diff = [];
if (is_array($array2[$key])) {
/*
* Let's verify the nested array
*/
$diff = self::arrayDiffRecursive($value, $array2[$key], $valuesOnly);
}
if (empty($diff)) {
continue;
}
$effect[$key] = $diff;
} elseif ($value !== $array2[$key]) {
/*
* Value is different than in 2nd array?
* OKay, I've got difference
*/
$effect[$key] = $value;
}
} else {
/*
* OKay, I've got difference
*/
$effect[$key] = $value;
}
}
return $effect;
}
/**
* Returns an index / key of given element in given array
*
* @param array $array The array to verify
* @param mixed $element The element who index / key is needed
* @return bool|null|mixed
*/
public static function getIndexOf(array $array, $element)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return false;
}
foreach ($array as $index => $value) {
if ($value === $element) {
return $index;
}
}
return null;
}
/**
* Returns an array with incremented indexes / keys
*
* @param array $array The array which indexes / keys should be incremented
* @param int|null $startIndex (optional) Index from which incrementation should be started. If not provided,
* the first index / key will be used.
* @param int $incrementStep (optional) Value used for incrementation. The step of incrementation.
* @return array|null
*/
public static function incrementIndexes(array $array, $startIndex = null, $incrementStep = 1)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$valuesToIncrement = [];
/*
* Start index not provided?
* Let's look for the first index / key of given array
*/
if (null === $startIndex) {
$startIndex = self::getFirstKey($array);
}
/*
* Is the start index a numeric value?
* Other indexes / keys cannot be incremented
*/
if (is_numeric($startIndex)) {
/*
* 1st step:
* Get values which indexes should be incremented and remove those values from given array
*/
foreach ($array as $index => $value) {
if ($index < $startIndex) {
continue;
}
$valuesToIncrement[$index] = $value;
unset($array[$index]);
}
/*
* 2nd step:
* Increment indexes of gathered values
*/
if (!empty($valuesToIncrement)) {
foreach ($valuesToIncrement as $oldIndex => $value) {
$newIndex = $oldIndex + $incrementStep;
$array[$newIndex] = $value;
}
}
}
return $array;
}
/**
* Returns next element of given array related to given element
*
* @param array $array The array with elements
* @param mixed $element Element for who next element should be returned
* @return null|mixed
*/
public static function getNextElement(array $array, $element)
{
return self::getNeighbour($array, $element);
}
/**
* Returns previous element of given array related to given element
*
* @param array $array The array with elements
* @param mixed $element Element for who previous element should be returned
* @return null|mixed
*/
public static function getPreviousElement(array $array, $element)
{
return self::getNeighbour($array, $element, false);
}
/**
* Returns information if given array is a multi dimensional array
*
* @param array $array The array to verify
* @return bool|null
*/
public static function isMultiDimensional(array $array)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
return count($array) !== count($array, COUNT_RECURSIVE);
}
/**
* Returns count of dimensions, maximum nesting level actually, in given array
*
* @param array $array The array to verify
* @return int
*/
public static function getDimensionsCount(array $array)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return 0;
}
$dimensionsCount = 1;
foreach ($array as $value) {
if (is_array($value)) {
/*
* I have to increment returned value, because that means we've got 1 level more (if the value is an
* array)
*/
$count = self::getDimensionsCount($value) + 1;
if ($count > $dimensionsCount) {
$dimensionsCount = $count;
}
}
}
return $dimensionsCount;
}
/**
* Returns non-empty values, e.g. without "" (empty string), null or []
*
* @param array $values The values to filter
* @return array|null
*/
public static function getNonEmptyValues(array $values)
{
/*
* No values?
* Nothing to do
*/
if (empty($values)) {
return null;
}
return array_filter($values, function ($value) {
$nonEmptyScalar = is_scalar($value) && '' !== $value;
$nonEmptyArray = is_array($value) && !empty($value);
return $nonEmptyScalar || $nonEmptyArray || is_object($value);
});
}
/**
* Returns non-empty values concatenated by given separator
*
* @param array $values The values to filter
* @param string $separator (optional) Separator used to implode the values. Default: ", ".
* @return string|null
*/
public static function getNonEmptyValuesAsString(array $values, $separator = ', ')
{
/*
* No elements?
* Nothing to do
*/
if (empty($values)) {
return null;
}
$nonEmpty = self::getNonEmptyValues($values);
/*
* No values?
* Nothing to do
*/
if (empty($nonEmpty)) {
return '';
}
return implode($separator, $nonEmpty);
}
/**
* Returns neighbour (next or previous element) for given element
*
* @param array $array The array with elements
* @param mixed $element Element for who next element should be returned
* @param bool $next (optional) If is set to true, returns next neighbour. Otherwise - previous.
* @return mixed|null
*/
private static function getNeighbour(array $array, $element, $next = true)
{
/*
* No elements?
* Nothing to do
*/
if (empty($array)) {
return null;
}
$noNext = $next && self::isLastElement($array, $element);
$noPrevious = !$next && self::isFirstElement($array, $element);
/*
* Previous neighbour should be returned and given element is first?
* OR
* Next neighbour should be returned and given element is last?
* OR
* No elements?
* OR
* Given element does not exist in given array?
*
* Nothing to do
*/
if ($noPrevious || $noNext || empty($array) || !in_array($element, $array, true)) {
return null;
}
$neighbourKey = null;
$keys = array_keys($array);
$elementKey = self::getIndexOf($array, $element);
$indexOfKey = self::getIndexOf($keys, $elementKey);
/*
* Index of element or of element's key is unknown?
* Probably the element does not exist in given array, so... nothing to do
*/
if (null === $elementKey || null === $indexOfKey) {
return null;
}
/*
* Looking for key of the neighbour (next or previous element)
*/
if ($next) {
++$indexOfKey;
} else {
--$indexOfKey;
}
/*
* Let's prepare key of the neighbour and...
* ...we've got the neighbour :)
*/
$neighbourKey = $keys[$indexOfKey];
return $array[$neighbourKey];
}
}