DistanceFilter.php 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Filter\Utils;
  4. use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
  5. use Doctrine\ORM\QueryBuilder;
  6. use Dunglas\ApiBundle\Api\ResourceInterface;
  7. use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractFilter;
  8. use JetBrains\PhpStorm\ArrayShape;
  9. /**
  10. * Add a distance filter to en entity
  11. *
  12. * To use it, add the following query :
  13. *
  14. * withinDistance=({latitude}, {longitude}, {distance})
  15. *
  16. * Where latitude and longitude are the coordinates of the origine point, and distance the maximum
  17. * distance in Km.
  18. *
  19. * /!\ The subject entity shall have a longitude and a latitude properties
  20. */
  21. final class DistanceFilter extends AbstractFilter
  22. {
  23. /**
  24. * API docs
  25. * @param string|ResourceInterface $resource
  26. * @return array[]
  27. */
  28. #[ArrayShape(['search' => "array"])]
  29. public function getDescription(string|ResourceInterface $resource): array
  30. {
  31. if (!property_exists($resource, 'latitude') || !property_exists($resource, 'longitude')) {
  32. throw new \RuntimeException('DistanceFilter can only used with resources having both latitude and longitude properties');
  33. }
  34. return [
  35. 'search' => [
  36. 'property' => 'withinDistance',
  37. 'type' => 'string',
  38. 'required' => false,
  39. 'swagger' => [
  40. 'description' => "Filtre une entity selon sa distance (km) à un point (latitude, longitude). " .
  41. "L'entité doit-elle aussi posséder des propriétés 'latitude' et 'longitude'." .
  42. "Pass the following query to use it : `withinDistance=({latitude}, {longitude}, {distance})`, " .
  43. "where {latitude} and {longitude} are the coordinates of the origine point, and {distance} the maximum distance in Km.",
  44. 'name' => 'Distance Filter',
  45. 'type' => 'Utils Filter',
  46. ],
  47. ]
  48. ];
  49. }
  50. protected function filterProperty(
  51. string $property,
  52. $value,
  53. QueryBuilder $queryBuilder,
  54. QueryNameGeneratorInterface
  55. $queryNameGenerator, string
  56. $resourceClass,
  57. string $operationName = null
  58. ): void
  59. {
  60. if ($property !== 'withinDistance') {
  61. return;
  62. }
  63. if (!preg_match('/^(\d+(\.\d+)?,){2}\d+(\.\d+)?$/', $value)) {
  64. throw new \RuntimeException('DistanceFilter : Invalid argument, please pass latitude, longitude and distance to the parameter as comma separated floating numbers.');
  65. }
  66. [$latitude, $longitude, $distance] = explode(',', $value);
  67. $alias = $queryBuilder->getRootAliases()[0];
  68. // Generate unique parameters names to avoid collisions with other filters
  69. $latitudeParameterName = $queryNameGenerator->generateParameterName('latitude');
  70. $longitudeParameterName = $queryNameGenerator->generateParameterName('longitude');
  71. $distanceParameterName = $queryNameGenerator->generateParameterName('distance');
  72. $queryBuilder
  73. ->andWhere(
  74. sprintf(
  75. 'SPHERICAL_DISTANCE(%1$s.latitude, %1$s.longitude, :%2$s, :%3$s) <= :%4$s',
  76. $alias, $latitudeParameterName, $longitudeParameterName, $distanceParameterName
  77. )
  78. )
  79. ->setParameter($latitudeParameterName, $latitude)
  80. ->setParameter($longitudeParameterName, $longitude)
  81. ->setParameter($distanceParameterName, $distance);
  82. }
  83. }