src/helpers/ArrayHelper.php
<?php
namespace luya\yii\helpers;
use yii\helpers\BaseArrayHelper;
/**
* Helper methods when dealing with Arrays.
*
* Extends the {{yii\helpers\ArrayHelper}} class by some useful functions like:
*
* + {{luya\yii\helpers\ArrayHelper::toObject()}}
* + {{luya\yii\helpers\ArrayHelper::arrayUnshiftAssoc()}}
* + {{luya\yii\helpers\ArrayHelper::typeCast()}}
* + {{luya\yii\helpers\ArrayHelper::search()}}
* + {{luya\yii\helpers\ArrayHelper::searchColumn()}}
* + {{luya\yii\helpers\ArrayHelper::searchColumns()}}
* + {{luya\yii\helpers\ArrayHelper::generateRange()}}
*
* @author Basil Suter <basil@nadar.io>
* @since 1.0.0
*/
class ArrayHelper extends BaseArrayHelper
{
/**
* @var array An array with sensitive keys which are used if no keys will be passed to {{luya\yii\helpers\ArrayHelper::coverSensitiveValues()}}.
*/
public static $sensitiveDefaultKeys = ['password', 'pwd', 'pass', 'passwort', 'pw', 'token', 'hash', 'authorization', 'auth'];
/**
* Create an object from an array.
*
* @param array $array
* @return object
*/
public static function toObject(array $array)
{
return json_decode(json_encode($array), false, 512);
}
/**
* Cover sensitive values from a given list of keys.
*
* The main purpose is to remove passwords transferred to API when existing in POST, GET or SESSION.
*
* Example:
*
* ```php
* $data = ArrayHelper::coverSensitiveValues(['username' => 'foo', 'password' => 'bar'], ['password']];
*
* var_dump($data); // array('username' => 'foo', 'password' => '***');
* ```
*
* @param array $data The input data to cover given sensitive key values. `['username' => 'foo', 'password' => 'bar']`.
* @param array $keys The keys which can contain sensitive data inside the $data array. `['password', 'pwd', 'pass']` if no keys provided the {{luya\yii\helpers\ArrayHelper::$sensitiveDefaultKeys}} is used.
*/
public static function coverSensitiveValues(array $data, array $keys = [])
{
if (empty($keys)) {
$keys = self::$sensitiveDefaultKeys;
}
$clean = [];
foreach ($keys as $key) {
$kw = strtolower($key);
foreach ($data as $k => $v) {
if (is_array($v)) {
$clean[$k] = static::coverSensitiveValues($v, $keys);
} elseif (is_scalar($v) && ($kw == strtolower($k) || StringHelper::startsWith(strtolower($k), $kw))) {
$v = str_repeat("*", strlen($v));
$clean[$k] = $v;
}
}
}
// the latter overrides the former
return array_replace($data, $clean);
}
/**
* Prepend an associative array item as first entry for a given array.
*
* Adds the given key with value as first entry to $arr
*
* @param array $arr The array where the value should be prepend
* @param string $key The new array key
* @param mixed $val The value for the new key
* @return array
*/
public static function arrayUnshiftAssoc(&$arr, $key, $val)
{
$arr = array_reverse($arr, true);
$arr[$key] = $val;
return array_reverse($arr, true);
}
/**
* TypeCast values from a mixed array source. numeric values will be casted as integer.
*
* This method is often used to convert correct JSON response arrays
*
* @param array $array The array which should be type casted
* @return array An array with type casted values
*/
public static function typeCast(array $array)
{
$return = [];
foreach ($array as $k => $v) {
if (is_numeric($v)) {
$return[$k] = StringHelper::typeCastNumeric($v);
} elseif (is_array($v)) {
$return[$k] = self::typeCast($v);
} else {
$return[$k] = $v;
}
}
return $return;
}
/**
* Search through all keys inside of an array, any occurrence will return the rest of the array.
*
* ```php
* $data = [
* ['name' => 'Foo Bar', 'id' => 1],
* ['name' => 'Bar Baz', 'id' => 2],
* ];
* ```
*
* Assuming the above array the expression `ArrayHelper::search($data, 1)` would return:
*
* ```php
* $data = [
* ['name' => 'Foo Bar', 'id' => 1],
* ];
* ```
*
* Searching for the string `Bar` would return the original array if bar would be found in both.
*
* In order to search only in certain keys defined $keys attribute:
*
* ```php
* ArrayHelper::search($data, 'Foo', false, ['name']);
* ```
*
* The above example will search only in the array key `name` for the `Foo` expression.
*
* @param array $array The multidimensional array keys.
* @param string $searchText The text you where search inside the rows.
* @param boolean $sensitive Whether to use strict sensitive search (`true`) or case insensitive search (`false`).
* @param array $keys A list of array keys which should be taken to search in, if empty or not provided it will lookup all array keys by default.
* @return array The modified array depending on the search result hits.
*/
public static function search(array $array, $searchText, $sensitive = false, array $keys = [])
{
$function = $sensitive ? 'strpos' : 'stripos';
return array_filter($array, function ($item) use ($searchText, $function, $keys) {
$response = false;
foreach ($item as $key => $value) {
if ($response) {
continue;
}
if (!empty($keys) && !in_array($key, $keys)) {
continue;
}
if ($function($value, "$searchText") !== false) {
$response = true;
}
}
return $response;
});
}
/**
* Search for a column value inside a multidimensional array and return the array with the found key.
*
* Compared to `searchColumns()` this function return will return the first found result.
*
* ```php
* $array = [
* ['name' => 'luya', 'userId' => 1],
* ['name' => 'nadar', 'userId' => 2],
* ];
*
* $result = ArrayHelper::searchColumn($array, 'name', 'nadar');
*
* // output:
* // array ('name' => 'nadar', 'userId' => 2);
* ```
*
* > This will not work with associative keys
*
* @param array $array The array with the multimensional array values.
* @param string $column The column to lookup and compare with the $search string.
* @param string $search The string to search inside the provided column.
* @return array|boolean
*/
public static function searchColumn(array $array, $column, $search)
{
$array = array_values($array); // align array keys
$columns = array_column($array, $column);
$key = array_search($search, $columns);
return ($key !== false) ? $array[$key] : false;
}
/**
* Search for columns with the given search value, returns the full array with all valid items.
*
* Compared to `searchColumn()` this function return will return all found results.
*
* > This function is not case-sensitive, which means `FOO` will match `Foo`, `foo` and `FOO`.
*
* ```php
* $array = [
* ['name' => 'luya', 'userId' => 1],
* ['name' => 'nadar', 'userId' => 1],
* ];
*
* $result = ArrayHelper::searchColumns($array, 'userId', '1');
*
* // output:
* // array (
* // array ('name' => 'luya', 'userId' => 1),
* // array ('name' => 'nadar', 'userId' => 1)
* // );
* ```
*
* @param array $array The multidimensional array input
* @param string $column The column to compare with $search string
* @param mixed $search The search string to compare with the column value.
* @return array Returns an array with all valid elements.
*/
public static function searchColumns(array $array, $column, $search)
{
$keys = array_filter($array, function ($var) use ($column, $search) {
return strcasecmp($search, (string) $var[$column]) == 0 ? true : false;
});
return $keys;
}
/**
* Generate an array from a range with an appending optional text.
*
* This is commonly used when generate dropdowns in forms to select a number of something.
*
* When $text is an array, the first key is the singular value to use, the second is the pluralized value.
*
* ```php
* $range = ArrayHelper::generateRange(1, 3, 'ticket');
* // array (1 => "1 ticket", 2 => "2 ticket", 3 => "3 ticket")
* ```
*
* Using the pluralized texts:
*
* ```php
* $range = ArrayHelper::generateRange(1, 3, ['ticket', 'tickets']);
* // array (1 => "1 ticket", 2 => "2 tickets", 3 => "3 tickets")
* ```
*
* PHP's `range()` function is used to generate the array range.
*
* @param string|integer $from The range starts from
* @param string|integer $to The range ends
* @param string|array $text Optinal text to append to each element. If an array is given the first value is used
* for the singular value, the second will be used for the pluralized values.
* @return array An array where the key is the number and value the number with optional text.
*/
public static function generateRange($from, $to, $text = null)
{
$range = range($from, $to);
$array = array_combine($range, $range);
if ($text) {
array_walk($array, function (&$item, $key) use ($text) {
if (is_array($text)) {
list($singular, $plural) = $text;
if ($key == 1) {
$item = "{$key} {$singular}";
} else {
$item = "{$key} {$plural}";
}
} else {
$item = "{$key} {$text}";
}
});
}
return $array;
}
/**
* Helper method to generate an array with the same keys and values.
*
* ```php
* $data = ArrayHelper::combine(['foo', 'bar']);
*
* // generates
* ['foo' => 'foo', 'bar' => 'bar'];
* ```
*
* @param array $array The array to combine.
* @return array
*/
public static function combine(array $array)
{
return array_combine($array, $array);
}
}