Utils.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. <?php
  2. namespace JmesPath;
  3. class Utils
  4. {
  5. public static $typeMap = [
  6. 'boolean' => 'boolean',
  7. 'string' => 'string',
  8. 'NULL' => 'null',
  9. 'double' => 'number',
  10. 'float' => 'number',
  11. 'integer' => 'number'
  12. ];
  13. /**
  14. * Returns true if the value is truthy
  15. *
  16. * @param mixed $value Value to check
  17. *
  18. * @return bool
  19. */
  20. public static function isTruthy($value)
  21. {
  22. if (!$value) {
  23. return $value === 0 || $value === '0';
  24. } elseif ($value instanceof \stdClass) {
  25. return (bool) get_object_vars($value);
  26. } else {
  27. return true;
  28. }
  29. }
  30. /**
  31. * Gets the JMESPath type equivalent of a PHP variable.
  32. *
  33. * @param mixed $arg PHP variable
  34. * @return string Returns the JSON data type
  35. * @throws \InvalidArgumentException when an unknown type is given.
  36. */
  37. public static function type($arg)
  38. {
  39. $type = gettype($arg);
  40. if (isset(self::$typeMap[$type])) {
  41. return self::$typeMap[$type];
  42. } elseif ($type === 'array') {
  43. if (empty($arg)) {
  44. return 'array';
  45. }
  46. reset($arg);
  47. return key($arg) === 0 ? 'array' : 'object';
  48. } elseif ($arg instanceof \stdClass) {
  49. return 'object';
  50. } elseif ($arg instanceof \Closure) {
  51. return 'expression';
  52. } elseif ($arg instanceof \ArrayAccess
  53. && $arg instanceof \Countable
  54. ) {
  55. return count($arg) == 0 || $arg->offsetExists(0)
  56. ? 'array'
  57. : 'object';
  58. } elseif (method_exists($arg, '__toString')) {
  59. return 'string';
  60. }
  61. throw new \InvalidArgumentException(
  62. 'Unable to determine JMESPath type from ' . get_class($arg)
  63. );
  64. }
  65. /**
  66. * Determine if the provided value is a JMESPath compatible object.
  67. *
  68. * @param mixed $value
  69. *
  70. * @return bool
  71. */
  72. public static function isObject($value)
  73. {
  74. if (is_array($value)) {
  75. return !$value || array_keys($value)[0] !== 0;
  76. }
  77. // Handle array-like values. Must be empty or offset 0 does not exist
  78. return $value instanceof \Countable && $value instanceof \ArrayAccess
  79. ? count($value) == 0 || !$value->offsetExists(0)
  80. : $value instanceof \stdClass;
  81. }
  82. /**
  83. * Determine if the provided value is a JMESPath compatible array.
  84. *
  85. * @param mixed $value
  86. *
  87. * @return bool
  88. */
  89. public static function isArray($value)
  90. {
  91. if (is_array($value)) {
  92. return !$value || array_keys($value)[0] === 0;
  93. }
  94. // Handle array-like values. Must be empty or offset 0 exists.
  95. return $value instanceof \Countable && $value instanceof \ArrayAccess
  96. ? count($value) == 0 || $value->offsetExists(0)
  97. : false;
  98. }
  99. /**
  100. * JSON aware value comparison function.
  101. *
  102. * @param mixed $a First value to compare
  103. * @param mixed $b Second value to compare
  104. *
  105. * @return bool
  106. */
  107. public static function isEqual($a, $b)
  108. {
  109. if ($a === $b) {
  110. return true;
  111. } elseif ($a instanceof \stdClass) {
  112. return self::isEqual((array) $a, $b);
  113. } elseif ($b instanceof \stdClass) {
  114. return self::isEqual($a, (array) $b);
  115. } else {
  116. return false;
  117. }
  118. }
  119. /**
  120. * JMESPath requires a stable sorting algorithm, so here we'll implement
  121. * a simple Schwartzian transform that uses array index positions as tie
  122. * breakers.
  123. *
  124. * @param array $data List or map of data to sort
  125. * @param callable $sortFn Callable used to sort values
  126. *
  127. * @return array Returns the sorted array
  128. * @link http://en.wikipedia.org/wiki/Schwartzian_transform
  129. */
  130. public static function stableSort(array $data, callable $sortFn)
  131. {
  132. // Decorate each item by creating an array of [value, index]
  133. array_walk($data, function (&$v, $k) {
  134. $v = [$v, $k];
  135. });
  136. // Sort by the sort function and use the index as a tie-breaker
  137. uasort($data, function ($a, $b) use ($sortFn) {
  138. return $sortFn($a[0], $b[0]) ?: ($a[1] < $b[1] ? -1 : 1);
  139. });
  140. // Undecorate each item and return the resulting sorted array
  141. return array_map(function ($v) {
  142. return $v[0];
  143. }, array_values($data));
  144. }
  145. /**
  146. * Creates a Python-style slice of a string or array.
  147. *
  148. * @param array|string $value Value to slice
  149. * @param int|null $start Starting position
  150. * @param int|null $stop Stop position
  151. * @param int $step Step (1, 2, -1, -2, etc.)
  152. *
  153. * @return array|string
  154. * @throws \InvalidArgumentException
  155. */
  156. public static function slice($value, $start = null, $stop = null, $step = 1)
  157. {
  158. if (!is_array($value) && !is_string($value)) {
  159. throw new \InvalidArgumentException('Expects string or array');
  160. }
  161. return self::sliceIndices($value, $start, $stop, $step);
  162. }
  163. private static function adjustEndpoint($length, $endpoint, $step)
  164. {
  165. if ($endpoint < 0) {
  166. $endpoint += $length;
  167. if ($endpoint < 0) {
  168. $endpoint = $step < 0 ? -1 : 0;
  169. }
  170. } elseif ($endpoint >= $length) {
  171. $endpoint = $step < 0 ? $length - 1 : $length;
  172. }
  173. return $endpoint;
  174. }
  175. private static function adjustSlice($length, $start, $stop, $step)
  176. {
  177. if ($step === null) {
  178. $step = 1;
  179. } elseif ($step === 0) {
  180. throw new \RuntimeException('step cannot be 0');
  181. }
  182. if ($start === null) {
  183. $start = $step < 0 ? $length - 1 : 0;
  184. } else {
  185. $start = self::adjustEndpoint($length, $start, $step);
  186. }
  187. if ($stop === null) {
  188. $stop = $step < 0 ? -1 : $length;
  189. } else {
  190. $stop = self::adjustEndpoint($length, $stop, $step);
  191. }
  192. return [$start, $stop, $step];
  193. }
  194. private static function sliceIndices($subject, $start, $stop, $step)
  195. {
  196. $type = gettype($subject);
  197. $len = $type == 'string' ? mb_strlen($subject, 'UTF-8') : count($subject);
  198. list($start, $stop, $step) = self::adjustSlice($len, $start, $stop, $step);
  199. $result = [];
  200. if ($step > 0) {
  201. for ($i = $start; $i < $stop; $i += $step) {
  202. $result[] = $subject[$i];
  203. }
  204. } else {
  205. for ($i = $start; $i > $stop; $i += $step) {
  206. $result[] = $subject[$i];
  207. }
  208. }
  209. return $type == 'string' ? implode('', $result) : $result;
  210. }
  211. }