vendor/knplabs/knp-components/src/Knp/Component/Pager/Event/Subscriber/Sortable/ArraySubscriber.php line 43

Open in your IDE?
  1. <?php
  2. namespace Knp\Component\Pager\Event\Subscriber\Sortable;
  3. use Knp\Component\Pager\Event\ItemsEvent;
  4. use Knp\Component\Pager\PaginatorInterface;
  5. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  6. use Symfony\Component\HttpFoundation\Request;
  7. use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
  8. use Symfony\Component\PropertyAccess\PropertyAccess;
  9. use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
  10. class ArraySubscriber implements EventSubscriberInterface
  11. {
  12. /**
  13. * @var string the field used to sort current object array list
  14. */
  15. private string $currentSortingField;
  16. /**
  17. * @var string the sorting direction
  18. */
  19. private string $sortDirection;
  20. private ?PropertyAccessorInterface $propertyAccessor;
  21. private Request $request;
  22. public function __construct(Request $request = null, PropertyAccessorInterface $accessor = null)
  23. {
  24. if (!$accessor && class_exists(PropertyAccess::class)) {
  25. $accessor = PropertyAccess::createPropertyAccessorBuilder()->enableMagicCall()->getPropertyAccessor();
  26. }
  27. $this->propertyAccessor = $accessor;
  28. // check needed because $request must be nullable, being the second parameter (with the first one nullable)
  29. if (null === $request) {
  30. throw new \InvalidArgumentException('Request must be initialized.');
  31. }
  32. $this->request = $request;
  33. }
  34. public function items(ItemsEvent $event): void
  35. {
  36. // Check if the result has already been sorted by an other sort subscriber
  37. $customPaginationParameters = $event->getCustomPaginationParameters();
  38. if (!empty($customPaginationParameters['sorted']) ) {
  39. return;
  40. }
  41. $sortField = $event->options[PaginatorInterface::SORT_FIELD_PARAMETER_NAME];
  42. if (!is_array($event->target) || null === $sortField || !$this->request->query->has($sortField)) {
  43. return;
  44. }
  45. $event->setCustomPaginationParameter('sorted', true);
  46. if (isset($event->options[PaginatorInterface::SORT_FIELD_ALLOW_LIST]) && !in_array($this->request->query->get($sortField), $event->options[PaginatorInterface::SORT_FIELD_ALLOW_LIST])) {
  47. throw new \UnexpectedValueException("Cannot sort by: [{$this->request->query->get($sortField)}] this field is not in allow list.");
  48. }
  49. $sortFunction = $event->options['sortFunction'] ?? [$this, 'proxySortFunction'];
  50. $sortField = $this->request->query->get($sortField);
  51. // compatibility layer
  52. if ($sortField[0] === '.') {
  53. $sortField = substr($sortField, 1);
  54. }
  55. call_user_func_array($sortFunction, [
  56. &$event->target,
  57. $sortField,
  58. $this->getSortDirection($event->options),
  59. ]);
  60. }
  61. private function getSortDirection(array $options): string
  62. {
  63. if (!$this->request->query->has($options[PaginatorInterface::SORT_DIRECTION_PARAMETER_NAME])) {
  64. return 'desc';
  65. }
  66. $direction = $this->request->query->get($options[PaginatorInterface::SORT_DIRECTION_PARAMETER_NAME]);
  67. if (strtolower($direction) === 'asc') {
  68. return 'asc';
  69. }
  70. return 'desc';
  71. }
  72. private function proxySortFunction(&$target, $sortField, $sortDirection): bool
  73. {
  74. $this->currentSortingField = $sortField;
  75. $this->sortDirection = $sortDirection;
  76. return usort($target, [$this, 'sortFunction']);
  77. }
  78. /**
  79. * @param mixed $object1 first object to compare
  80. * @param mixed $object2 second object to compare
  81. *
  82. * @return int
  83. */
  84. private function sortFunction($object1, $object2): int
  85. {
  86. if (null === $this->propertyAccessor) {
  87. throw new \UnexpectedValueException('You need symfony/property-access component to use this sorting function');
  88. }
  89. if (!$this->propertyAccessor->isReadable($object1, $this->currentSortingField) || !$this->propertyAccessor->isReadable($object2, $this->currentSortingField)) {
  90. return 0;
  91. }
  92. try {
  93. $fieldValue1 = $this->propertyAccessor->getValue($object1, $this->currentSortingField);
  94. } catch (UnexpectedTypeException $e) {
  95. return -1 * $this->getSortCoefficient();
  96. }
  97. try {
  98. $fieldValue2 = $this->propertyAccessor->getValue($object2, $this->currentSortingField);
  99. } catch (UnexpectedTypeException $e) {
  100. return 1 * $this->getSortCoefficient();
  101. }
  102. if (is_string($fieldValue1)) {
  103. $fieldValue1 = mb_strtolower($fieldValue1);
  104. }
  105. if (is_string($fieldValue2)) {
  106. $fieldValue2 = mb_strtolower($fieldValue2);
  107. }
  108. if ($fieldValue1 === $fieldValue2) {
  109. return 0;
  110. }
  111. return ($fieldValue1 > $fieldValue2 ? 1 : -1) * $this->getSortCoefficient();
  112. }
  113. private function getSortCoefficient(): int
  114. {
  115. return $this->sortDirection === 'asc' ? 1 : -1;
  116. }
  117. public static function getSubscribedEvents(): array
  118. {
  119. return [
  120. 'knp_pager.items' => ['items', 1],
  121. ];
  122. }
  123. }