ObjectExpressionVisitor.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. <?php
  2. /**
  3. * @package Grav\Framework\Object
  4. *
  5. * @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Framework\Object\Collection;
  9. use ArrayAccess;
  10. use Closure;
  11. use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor;
  12. use Doctrine\Common\Collections\Expr\Comparison;
  13. use RuntimeException;
  14. use function in_array;
  15. use function is_array;
  16. use function is_callable;
  17. use function is_string;
  18. use function strlen;
  19. /**
  20. * Class ObjectExpressionVisitor
  21. * @package Grav\Framework\Object\Collection
  22. */
  23. class ObjectExpressionVisitor extends ClosureExpressionVisitor
  24. {
  25. /**
  26. * Accesses the field of a given object.
  27. *
  28. * @param object $object
  29. * @param string $field
  30. * @return mixed
  31. */
  32. public static function getObjectFieldValue($object, $field)
  33. {
  34. $op = $value = null;
  35. $pos = strpos($field, '(');
  36. if (false !== $pos) {
  37. [$op, $field] = explode('(', $field, 2);
  38. $field = rtrim($field, ')');
  39. }
  40. if ($object instanceof ArrayAccess && isset($object[$field])) {
  41. $value = $object[$field];
  42. } else {
  43. $accessors = array('', 'get', 'is');
  44. foreach ($accessors as $accessor) {
  45. $accessor .= $field;
  46. if (!is_callable([$object, $accessor])) {
  47. continue;
  48. }
  49. $value = $object->{$accessor}();
  50. break;
  51. }
  52. }
  53. if ($op) {
  54. $function = 'filter' . ucfirst(strtolower($op));
  55. if (method_exists(static::class, $function)) {
  56. $value = static::$function($value);
  57. }
  58. }
  59. return $value;
  60. }
  61. /**
  62. * @param string $str
  63. * @return string
  64. */
  65. public static function filterLower($str)
  66. {
  67. return mb_strtolower($str);
  68. }
  69. /**
  70. * @param string $str
  71. * @return string
  72. */
  73. public static function filterUpper($str)
  74. {
  75. return mb_strtoupper($str);
  76. }
  77. /**
  78. * @param string $str
  79. * @return int
  80. */
  81. public static function filterLength($str)
  82. {
  83. return mb_strlen($str);
  84. }
  85. /**
  86. * @param string $str
  87. * @return string
  88. */
  89. public static function filterLtrim($str)
  90. {
  91. return ltrim($str);
  92. }
  93. /**
  94. * @param string $str
  95. * @return string
  96. */
  97. public static function filterRtrim($str)
  98. {
  99. return rtrim($str);
  100. }
  101. /**
  102. * @param string $str
  103. * @return string
  104. */
  105. public static function filterTrim($str)
  106. {
  107. return trim($str);
  108. }
  109. /**
  110. * Helper for sorting arrays of objects based on multiple fields + orientations.
  111. *
  112. * Comparison between two strings is natural and case insensitive.
  113. *
  114. * @param string $name
  115. * @param int $orientation
  116. * @param Closure|null $next
  117. *
  118. * @return Closure
  119. */
  120. public static function sortByField($name, $orientation = 1, Closure $next = null)
  121. {
  122. if (!$next) {
  123. $next = function ($a, $b) {
  124. return 0;
  125. };
  126. }
  127. return function ($a, $b) use ($name, $next, $orientation) {
  128. $aValue = static::getObjectFieldValue($a, $name);
  129. $bValue = static::getObjectFieldValue($b, $name);
  130. if ($aValue === $bValue) {
  131. return $next($a, $b);
  132. }
  133. // For strings we use natural case insensitive sorting.
  134. if (is_string($aValue) && is_string($bValue)) {
  135. return strnatcasecmp($aValue, $bValue) * $orientation;
  136. }
  137. return (($aValue > $bValue) ? 1 : -1) * $orientation;
  138. };
  139. }
  140. /**
  141. * {@inheritDoc}
  142. */
  143. public function walkComparison(Comparison $comparison)
  144. {
  145. $field = $comparison->getField();
  146. $value = $comparison->getValue()->getValue(); // shortcut for walkValue()
  147. switch ($comparison->getOperator()) {
  148. case Comparison::EQ:
  149. return function ($object) use ($field, $value) {
  150. return static::getObjectFieldValue($object, $field) === $value;
  151. };
  152. case Comparison::NEQ:
  153. return function ($object) use ($field, $value) {
  154. return static::getObjectFieldValue($object, $field) !== $value;
  155. };
  156. case Comparison::LT:
  157. return function ($object) use ($field, $value) {
  158. return static::getObjectFieldValue($object, $field) < $value;
  159. };
  160. case Comparison::LTE:
  161. return function ($object) use ($field, $value) {
  162. return static::getObjectFieldValue($object, $field) <= $value;
  163. };
  164. case Comparison::GT:
  165. return function ($object) use ($field, $value) {
  166. return static::getObjectFieldValue($object, $field) > $value;
  167. };
  168. case Comparison::GTE:
  169. return function ($object) use ($field, $value) {
  170. return static::getObjectFieldValue($object, $field) >= $value;
  171. };
  172. case Comparison::IN:
  173. return function ($object) use ($field, $value) {
  174. return in_array(static::getObjectFieldValue($object, $field), $value, true);
  175. };
  176. case Comparison::NIN:
  177. return function ($object) use ($field, $value) {
  178. return !in_array(static::getObjectFieldValue($object, $field), $value, true);
  179. };
  180. case Comparison::CONTAINS:
  181. return function ($object) use ($field, $value) {
  182. return false !== strpos(static::getObjectFieldValue($object, $field), $value);
  183. };
  184. case Comparison::MEMBER_OF:
  185. return function ($object) use ($field, $value) {
  186. $fieldValues = static::getObjectFieldValue($object, $field);
  187. if (!is_array($fieldValues)) {
  188. $fieldValues = iterator_to_array($fieldValues);
  189. }
  190. return in_array($value, $fieldValues, true);
  191. };
  192. case Comparison::STARTS_WITH:
  193. return function ($object) use ($field, $value) {
  194. return 0 === strpos(static::getObjectFieldValue($object, $field), $value);
  195. };
  196. case Comparison::ENDS_WITH:
  197. return function ($object) use ($field, $value) {
  198. return $value === substr(static::getObjectFieldValue($object, $field), -strlen($value));
  199. };
  200. default:
  201. throw new RuntimeException('Unknown comparison operator: ' . $comparison->getOperator());
  202. }
  203. }
  204. }