<?php
namespace JmesPath;
class Utils
{
static $typeMap = [
'boolean' => 'boolean',
'string' => 'string',
'NULL' => 'null',
'double' => 'number',
'float' => 'number',
'integer' => 'number'
];
/**
* Returns true if the value is truthy
*
* @param mixed $value Value to check
*
* @return bool
*/
public static function isTruthy($value)
{
if (!$value) {
return $value === 0 || $value === '0';
} elseif ($value instanceof \stdClass) {
return (bool) get_object_vars($value);
} else {
return true;
}
}
/**
* Gets the JMESPath type equivalent of a PHP variable.
*
* @param mixed $arg PHP variable
* @return string Returns the JSON data type
* @throws \InvalidArgumentException when an unknown type is given.
*/
public static function type($arg)
{
$type = gettype($arg);
if (isset(self::$typeMap[$type])) {
return self::$typeMap[$type];
} elseif ($type === 'array') {
if (empty($arg)) {
return 'array';
}
reset($arg);
return key($arg) === 0 ? 'array' : 'object';
} elseif ($arg instanceof \stdClass) {
return 'object';
} elseif ($arg instanceof \Closure) {
return 'expression';
} elseif ($arg instanceof \ArrayAccess
&& $arg instanceof \Countable
) {
return count($arg) == 0 || $arg->offsetExists(0)
? 'array'
: 'object';
} elseif (method_exists($arg, '__toString')) {
return 'string';
}
throw new \InvalidArgumentException(
'Unable to determine JMESPath type from ' . get_class($arg)
);
}
/**
* Determine if the provided value is a JMESPath compatible object.
*
* @param mixed $value
*
* @return bool
*/
public static function isObject($value)
{
if (is_array($value)) {
return !$value || array_keys($value)[0] !== 0;
}
// Handle array-like values. Must be empty or offset 0 does not exist
return $value instanceof \Countable && $value instanceof \ArrayAccess
? count($value) == 0 || !$value->offsetExists(0)
: $value instanceof \stdClass;
}
/**
* Determine if the provided value is a JMESPath compatible array.
*
* @param mixed $value
*
* @return bool
*/
public static function isArray($value)
{
if (is_array($value)) {
return !$value || array_keys($value)[0] === 0;
}
// Handle array-like values. Must be empty or offset 0 exists.
return $value instanceof \Countable && $value instanceof \ArrayAccess
? count($value) == 0 || $value->offsetExists(0)
: false;
}
/**
* JSON aware value comparison function.
*
* @param mixed $a First value to compare
* @param mixed $b Second value to compare
*
* @return bool
*/
public static function isEqual($a, $b)
{
if ($a === $b) {
return true;
} elseif ($a instanceof \stdClass) {
return self::isEqual((array) $a, $b);
} elseif ($b instanceof \stdClass) {
return self::isEqual($a, (array) $b);
} else {
return false;
}
}
/**
* JMESPath requires a stable sorting algorithm, so here we'll implement
* a simple Schwartzian transform that uses array index positions as tie
* breakers.
*
* @param array $data List or map of data to sort
* @param callable $sortFn Callable used to sort values
*
* @return array Returns the sorted array
* @link http://en.wikipedia.org/wiki/Schwartzian_transform
*/
public static function stableSort(array $data, callable $sortFn)
{
// Decorate each item by creating an array of [value, index]
array_walk($data, function (&$v, $k) { $v = [$v, $k]; });
// Sort by the sort function and use the index as a tie-breaker
uasort($data, function ($a, $b) use ($sortFn) {
return $sortFn($a[0], $b[0]) ?: ($a[1] < $b[1] ? -1 : 1);
});
// Undecorate each item and return the resulting sorted array
return array_map(function ($v) { return $v[0]; }, array_values($data));
}
/**
* Creates a Python-style slice of a string or array.
*
* @param array|string $value Value to slice
* @param int|null $start Starting position
* @param int|null $stop Stop position
* @param int $step Step (1, 2, -1, -2, etc.)
*
* @return array|string
* @throws \InvalidArgumentException
*/
public static function slice($value, $start = null, $stop = null, $step = 1)
{
if (!is_array($value) && !is_string($value)) {
throw new \InvalidArgumentException('Expects string or array');
}
return self::sliceIndices($value, $start, $stop, $step);
}
private static function adjustEndpoint($length, $endpoint, $step)
{
if ($endpoint < 0) {
$endpoint += $length;
if ($endpoint < 0) {
$endpoint = $step < 0 ? -1 : 0;
}
} elseif ($endpoint >= $length) {
$endpoint = $step < 0 ? $length - 1 : $length;
}
return $endpoint;
}
private static function adjustSlice($length, $start, $stop, $step)
{
if ($step === null) {
$step = 1;
} elseif ($step === 0) {
throw new \RuntimeException('step cannot be 0');
}
if ($start === null) {
$start = $step < 0 ? $length - 1 : 0;
} else {
$start = self::adjustEndpoint($length, $start, $step);
}
if ($stop === null) {
$stop = $step < 0 ? -1 : $length;
} else {
$stop = self::adjustEndpoint($length, $stop, $step);
}
return [$start, $stop, $step];
}
private static function sliceIndices($subject, $start, $stop, $step)
{
$type = gettype($subject);
$len = $type == 'string' ? strlen($subject) : count($subject);
list($start, $stop, $step) = self::adjustSlice($len, $start, $stop, $step);
$result = [];
if ($step > 0) {
for ($i = $start; $i < $stop; $i += $step) {
$result[] = $subject[$i];
}
} else {
for ($i = $start; $i > $stop; $i += $step) {
$result[] = $subject[$i];
}
}
return $type == 'string' ? implode($result, '') : $result;
}
}
|