【问题标题】:How to sanitize output with custom filter (API Platform)如何使用自定义过滤器清理输出(API 平台)
【发布时间】:2021-10-03 04:04:09
【问题描述】:

我正在尝试使用 api-platform (2.6.5) 在我的实体上添加距离过滤器。一切正常,除了响应正文:

"hydra:member": [
    {
        "0": {
            "@id": "/api/places/1",
            "@type": "Place",
            "id": 1,
            "name": "test",
            "description": "test",
            "slug": "test",
            "address": "test",
            "country": "Suisse",
            "latitude": "50.6300000",
            "longitude": "3.0620000",
            "tableCount": 10,
            "tablePlace": 6,
            "tableCapacity": 60
        },
        "distance": "8.887448516728334"
    },
    {
        "0": {
            "@id": "/api/places/8",
            ...
        },
        "distance": "10.273770606986691"
    },
    {...}

计算出来的距离不认为是实体属性,api平台单独返回(这是前端的问题)。

这是我的实体:

/**
* @ORM\Entity(repositoryClass=PlaceRepository::class)
 * @ApiResource(
 *     normalizationContext={"groups"={"place", "place:read"}, "swagger_definition_name": "Read"},
 *     denormalizationContext={"groups"={"place", "place:write"}, "swagger_definition_name": "Write"},
 * )
 * @ApiFilter(SearchFilter::class, properties={"name": "partial", "description": "partial", "zip":"start", "city": "exact"})
 * @ApiFilter(NearbyFilter::class, properties={"location"})
 * @Entity @HasLifecycleCallbacks
 */
class Place
{
....
/**
 * @ORM\Column(type="decimal", precision=10, scale=7, nullable=true)
 * @Groups({"place"})
 */
private $latitude;

/**
 * @ORM\Column(type="decimal", precision=10, scale=7, nullable=true)
 * @Groups({"place"})
 */
private $longitude;
/**
 * @var float The distance calculated
 * @Groups({"place:read"})
 */
private $distance;

这是我的过滤器:

final class NearbyFilter extends AbstractContextAwareFilter
{
    protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null)
    {
        
        if ($property !== 'location') {
            return;
        }
        
        [$lat, $lng, $radius] = explode(',', $value);

        // add distance in DQL based on filter location point
        $queryBuilder
            ->addSelect('
                (6371.0 * acos (
                    cos ( radians(:latitude) )
                    * cos( radians(o.latitude) )
                    * cos( radians(o.longitude) - radians(:longitude) )
                    + sin ( radians(:latitude) )
                    * sin( radians(o.latitude) )
                )) AS distance')
            ->having('distance <= :radius')
            ->setParameter('radius', $radius)
            ->setParameter('latitude', $lat)
            ->setParameter('longitude', $lng)
            ->orderBy('distance')
        ;

    }

如您所见,我尝试添加未映射的属性“距离”,但没有效果:/ 我也尝试使用 Dto 和 Denormalizer 来解决这个问题,但没有成功(但我是新手)。

你有什么想法可以解决我的问题吗?

谢谢!

【问题讨论】:

  • 由于 API 平台依赖于 ORM 数据提供者来检索和确定 json+ld 上下文的数据类型,并且 ORM 将分离非映射字段。我的建议是使用PrePersist 和PreUpdate entity event listener or subscriber 执行计算并将与数据库的距离保存为持久十进制值。您可以将计算添加到实体中的 setter 方法中,而不是使用侦听器,但如果未设置所有值,则可能会造成一致性问题。
  • 否则您可能需要使用 Doctrine result mapping 将值应用于对象,而不是作为单独的结果。虽然我没有将它与 API-Platform 一起使用。
  • 您好,感谢您的帮助。我想我应该更准确一些。距离是根据输入参数计算的,因此我无法将其保存在实体上。结果映射似乎是一个解决方案,但我已经使用 DataProvider(已回答)。
  • 我也曾考虑过推荐一个自定义数据提供者,但更关注根本原因。

标签: symfony api-platform.com


【解决方案1】:

终于找到了DataProvider的解决方案:

namespace App\DataProvider;

use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use App\Entity\Place;

class PlaceDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface
{
    private $collectionDataProvider;
    public function __construct(CollectionDataProviderInterface $collectionDataProvider)
    {
        $this->collectionDataProvider = $collectionDataProvider;
    }

    public function getCollection(string $resourceClass, string $operationName = null, array $context = [])
    {
        $places = $this->collectionDataProvider->getCollection($resourceClass, $operationName, $context);

        foreach ($places as &$place) {
            if(!$place instanceof Place && is_array($place)) {
                $sanitizedPlace = $place[0];

                if( !is_null( $place['distance'] ) ){
                    $sanitizedPlace = $this->setDistance($sanitizedPlace, $place['distance']);
                }

                $place = $sanitizedPlace;
            }
        }
        
        return $places;
    }

    public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
    {
        return $resourceClass === Place::class;
    }

    public function setDistance(Place $place, $distance): Place
    {
        $place->setDistance($distance);
        
        return $place;
    }
}

谢谢

【讨论】:

    猜你喜欢
    • 2022-11-04
    • 2020-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-05
    相关资源
    最近更新 更多