Symfony Api平台在从持久层检索实体后过滤实体
我有一种情况,我需要在应用后从持久层中提取对象,然后对对象数据和另一个查询参数的过滤基进行一些数学运算 用例:获取给定经纬度10km半径范围内的所有位置 可以将其转换为api端点,如下所示: 我拥有以下实体的位置:Symfony Api平台在从持久层检索实体后过滤实体,symfony,api-platform.com,symfony-4.3,Symfony,Api Platform.com,Symfony 4.3,我有一种情况,我需要在应用后从持久层中提取对象,然后对对象数据和另一个查询参数的过滤基进行一些数学运算 用例:获取给定经纬度10km半径范围内的所有位置 可以将其转换为api端点,如下所示: 我拥有以下实体的位置: * @ApiFilter(SearchFilter::class, properties={ * "longitude": "start", * "latitude":"start", * "city":"partial", * "
* @ApiFilter(SearchFilter::class, properties={
* "longitude": "start",
* "latitude":"start",
* "city":"partial",
* "postal_code":"partial",
* "address":"partial",
* }
* )
class Location
{
...
public function withinDistance($latitude, $longitude, $distance):?bool
{
$location_distance=$this->distanceFrom($latitude,$longitude);
return $location_distance<=$distance;
}
}
为什么对返回的实体对象集合应用这样的筛选器,正确的做法是什么?我不这么认为
ORM过滤器在这种情况下会很有用 您可以在SQL中应用该条件,例如在实体存储库中
class YourEntityRepository {
public function findByLongLatDist(float lat, float long, float dist) {
// create your query builder here and return results
}
}
还可以使用MySQL查询来检索点。并且这个存储库使用特定的MySQL函数。我发现,通过编写一个脚本来过滤实体是很容易的,并且在从持久层检索实体之后过滤实体。这可能意味着我必须获取所有记录,然后过滤,这是非常昂贵的 正如所建议的,另一种选择是编写一个自定义过滤器来查找距离,如下所示: 定义距离过滤器: src/过滤器/距离过滤器 src/config/services.yaml 在位置实体上配置api筛选器:
namespace App\Entity;
use App\Dto\LocationOutput;
use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiFilter;
/**
* Location
*
* @ApiResource(
* collectionOperations={
* "get"={
* "path"="/getLocationList",
* "filters"={
* "location.distance_filter",
* "location.search_filter"
* }
* }
* },
* itemOperations={"get"},
* output=LocationOutput::class
* )
我不确定如何调用该实体存储库函数。如果我们想用php进行计算呢?不过,我将探讨您的想法,我只是想知道是否有可能在从数据库检索对象后对其进行过滤。谢谢你的礼物。我们可以试试这个
<?php
namespace App\Filter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\AbstractContextAwareFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use Doctrine\ORM\QueryBuilder;
final class DistanceFilter extends AbstractContextAwareFilter
{
const DISTANCE=10.0;
const LAT='latitude';
const LON='longitude';
private $appliedAlready=false;
protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
{
// otherwise filter is applied to order and page as well
if ($this->appliedAlready && !$this->isPropertyEnabled($property, $resourceClass) ) {
return;
}
//make sure latitude and longitude are part of specs
if(!($this->isPropertyMapped(self::LAT, $resourceClass) && $this->isPropertyMapped(self::LON, $resourceClass)) ){
return ;
}
$query=$this->requestStack->getCurrentRequest()->query;
$values=[];
foreach($this->properties as $prop=>$val){
$this->properties[$prop]=$query->get($prop,null);
}
//distance is optional
if($this->properties[self::LAT]!=null && $this->properties[self::LON]!=null){
if($this->properties['distance']==null)
$this->properties['distance']=self::DISTANCE;
}else{
//may be we should raise exception
return;
}
$this->appliedAlready=True;
// Generate a unique parameter name to avoid collisions with other filters
$latParam = $queryNameGenerator->generateParameterName(self::LAT);
$lonParam = $queryNameGenerator->generateParameterName(self::LON);
$distParam = $queryNameGenerator->generateParameterName('distance');
$locationWithinXKmDistance="(
6371.0 * acos (
cos ( radians(:$latParam) )
* cos( radians(o.latitude) )
* cos( radians(o.longitude) - radians(:$lonParam) )
+ sin ( radians(:$latParam) )
* sin( radians(o.latitude) )
)
)<=:$distParam";
$queryBuilder
->andWhere($locationWithinXKmDistance)
->setParameter($latParam, $this->properties[self::LAT])
->setParameter($lonParam, $this->properties[self::LON])
->setParameter($distParam, $this->properties['distance']);
}
// This function is only used to hook in documentation generators (supported by Swagger and Hydra)
public function getDescription(string $resourceClass): array
{
if (!$this->properties) {
return [];
}
$description = [];
foreach ($this->properties as $property => $strategy) {
$description["distance_$property"] = [
'property' => $property,
'type' => 'string',
'required' => false,
'swagger' => [
'description' => 'Find locations within given radius',
'name' => 'distance_filter',
'type' => 'filter',
],
];
}
return $description;
}
}
doctrine:
orm:
dql:
numeric_functions:
acos: DoctrineExtensions\Query\Mysql\Acos
cos: DoctrineExtensions\Query\Mysql\Cos
sin: DoctrineExtensions\Query\Mysql\Sin
radians: DoctrineExtensions\Query\Mysql\Radians
services:
....
App\Filter\DistanceFilter:
arguments: [ '@doctrine', '@request_stack', '@?logger', {latitude: ~, longitude: ~, distance: ~} ]
tags:
- { name: 'api_platform.filter', id: 'location.distance_filter' }
autowire: false
autoconfigure: false
app.location.search_filter:
parent: 'api_platform.doctrine.orm.search_filter'
arguments: [ {"city":"partial","postal_code":"partial","address":"partial"}]
tags: [ { name: 'api_platform.filter', id: 'location.search_filter' } ]
autowire: false
autoconfigure: false
namespace App\Entity;
use App\Dto\LocationOutput;
use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiFilter;
/**
* Location
*
* @ApiResource(
* collectionOperations={
* "get"={
* "path"="/getLocationList",
* "filters"={
* "location.distance_filter",
* "location.search_filter"
* }
* }
* },
* itemOperations={"get"},
* output=LocationOutput::class
* )