Utils.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. <?php
  2. namespace JmesPath;
  3. class Utils
  4. {
  5. 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) { $v = [$v, $k]; });
  134. // Sort by the sort function and use the index as a tie-breaker
  135. uasort($data, function ($a, $b) use ($sortFn) {
  136. return $sortFn($a[0], $b[0]) ?: ($a[1] < $b[1] ? -1 : 1);
  137. });
  138. // Undecorate each item and return the resulting sorted array
  139. return array_map(function ($v) { return $v[0]; }, array_values($data));
  140. }
  141. /**
  142. * Creates a Python-style slice of a string or array.
  143. *
  144. * @param array|string $value Value to slice
  145. * @param int|null $start Starting position
  146. * @param int|null $stop Stop position
  147. * @param int $step Step (1, 2, -1, -2, etc.)
  148. *
  149. * @return array|string
  150. * @throws \InvalidArgumentException
  151. */
  152. public static function slice($value, $start = null, $stop = null, $step = 1)
  153. {
  154. if (!is_array($value) && !is_string($value)) {
  155. throw new \InvalidArgumentException('Expects string or array');
  156. }
  157. return self::sliceIndices($value, $start, $stop, $step);
  158. }
  159. private static function adjustEndpoint($length, $endpoint, $step)
  160. {
  161. if ($endpoint < 0) {
  162. $endpoint += $length;
  163. if ($endpoint < 0) {
  164. $endpoint = $step < 0 ? -1 : 0;
  165. }
  166. } elseif ($endpoint >= $length) {
  167. $endpoint = $step < 0 ? $length - 1 : $length;
  168. }
  169. return $endpoint;
  170. }
  171. private static function adjustSlice($length, $start, $stop, $step)
  172. {
  173. if ($step === null) {
  174. $step = 1;
  175. } elseif ($step === 0) {
  176. throw new \RuntimeException('step cannot be 0');
  177. }
  178. if ($start === null) {
  179. $start = $step < 0 ? $length - 1 : 0;
  180. } else {
  181. $start = self::adjustEndpoint($length, $start, $step);
  182. }
  183. if ($stop === null) {
  184. $stop = $step < 0 ? -1 : $length;
  185. } else {
  186. $stop = self::adjustEndpoint($length, $stop, $step);
  187. }
  188. return [$start, $stop, $step];
  189. }
  190. private static function sliceIndices($subject, $start, $stop, $step)
  191. {
  192. $type = gettype($subject);
  193. $len = $type == 'string' ? strlen($subject) : count($subject);
  194. list($start, $stop, $step) = self::adjustSlice($len, $start, $stop, $step);
  195. $result = [];
  196. if ($step > 0) {
  197. for ($i = $start; $i < $stop; $i += $step) {
  198. $result[] = $subject[$i];
  199. }
  200. } else {
  201. for ($i = $start; $i > $stop; $i += $step) {
  202. $result[] = $subject[$i];
  203. }
  204. }
  205. return $type == 'string' ? implode($result, '') : $result;
  206. }
  207. }