api_platform 产生错误“找不到 uri [/index/_doc/_search] 和方法 [POST] 的处理程序”

分享于2022年07月17日 api-platform.com elasticsearch symfony 问答
【问题标题】:api_platform 产生错误“找不到 uri [/index/_doc/_search] 和方法 [POST] 的处理程序”(api_platform produces Error "no handler found for uri [/index/_doc/_search] and method [POST]")
【发布时间】:2022-01-16 22:20:53
【问题描述】:

当尝试通过 fos_elastica-bundle (v6.0.0) 将 elasticsearch (v7.9.3) 实现到我的 Symfony (v5.3.10) - 带有 api_platform (v2.6.6) 的应用程序时,我不断收到此错误:

"{"error":"no handler found for uri [//posts/_doc/_search] and method [POST]"}",

我的 api_platform.yaml 内容如下:

api_platform:
    [...]
    elasticsearch:
        hosts: [ '%env(ELASTICSEARCH_URL)%' ]
        mapping:
            App\Document\Post:
                index: posts

还有我的 fos_elastica.yaml:

fos_elastica:
    clients:
        default: { url: '%env(ELASTICSEARCH_URL)%' }
    indexes:
        posts:
            properties:
                id:
                    "type": "keyword"
                source: ~
                title: ~
                description: ~
                body: ~
                children: ~
                tags: ~
                originalContent: ~
            persistence:
                driver: mongodb
                model: App\Document\Post

通过调试 fos-elastica Bundle,我发现 Elastica-Connector 使用此请求正文正确触发了 [POST]-Request 到“/posts/_doc/_search”:

{"sort":[{"id":{"order":"asc"}}],"query":{"match_all":{}},"size":30,"from":0}

如果我使用 Kibana 开发工具控制台并触发相同的请求

POST /posts/_doc/_search
  {"sort":[{"id":{"order":"asc"}}],"query":{"match_all":{}},"size":30,"from":60}

我确实按预期从弹性搜索中获得了结果:

#! Deprecation: [types removal] Specifying types in search requests is deprecated.
    {
      "took" : 12,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 3082,
          "relation" : "eq"
        },
        "max_score" : null,
        "hits" : [
          {
            "_index" : "posts",
            "_type" : "_doc",
[...]

除了弃用通知,一切似乎都很好。

有谁知道为什么 fos_elastica-bundle 的 api_platform 集成无法按预期工作并不断返回“未找到处理程序”错误消息?

  • 您的 PHP 路径中是否有额外的前导 / ?两个 / s 从这里开始: //posts/_doc/_search' 让我紧张
  • 是的,在 api_platform 计算的路径中有一个前导 /。我不知道它来自哪里,但我怀疑它会导致所描述的错误。如果我使用 Kibana 开发工具控制台触发带有两个前导斜杠的请求(即 POST //posts/_doc/_search),我仍然会得到相同(正确)的响应。
  • 仍然没有找到解决方案。有人可以帮忙吗?

【解决方案1】:

我现在通过创建自定义 ApiResource - 过滤器来帮助自己

#[ApiFilter(FulltextFilter::class, arguments: ['index' => 'post'], properties: ['body','description','tag'])]

我的自定义过滤器实现了 ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\FilterInterface ,直接与 ElasticSearch 服务器通信,发送查询以搜索指定的索引(帖子),并向聚合构建器添加另一个 match() 指令,其中包含一组与原始搜索匹配的 ID:

index = $index;
        $this->properties = $properties;
        $this->client = $client;
        $this->searchParameterName = $searchParameterName;
        $this->maxResultsParameterName = $maxResultsParameterName;
    }
    public function getFilteredIds($searchterm, $index = null, $properties = null, $maxResults = null) {
        $matches = [];
        if (is_null($properties)) {
            $properties = array_keys($this->properties);
        }
        foreach ($properties as $propertyName) {
            array_push($matches, ['match'=>[$propertyName => $searchterm]]);
        }
        $queryObject = ['query' => ['bool' => ['should' => $matches]]];
        $queryObject['size'] = (int) $maxResults >0 ? (int) $maxResults : self::DEFAULT_MAX_RESULTS;

        $query = new Query();
        $response = $this->client->getIndex($index ?? $this->index)
            ->search($query->setRawQuery($queryObject))
            ->getResults();
        return array_map(function(Result $result) {return $result->getHit()['_source']['id'];}, $response);

    }

    public function apply(Builder $aggregationBuilder, string $resourceClass, string $operationName = null, array &$context = [])
    {
        $maxResults = $context['filters'][$this->maxResultsParameterName] ?? null;
        $searchterm = $context['filters'][$this->searchParameterName] ?? false;
        if ($searchterm !== false) {
            $aggregationBuilder->match()->field('id')->in($this->getFilteredIds($searchterm, null, null, $maxResults));
        }
    }

    public function getDescription(string $resourceClass): array
    {
        return [];
    }
}

此解决方案可能不如使用 api_platform 原生提供的 ElasticSearch-Connector 优雅,但它的性能相当好,并且可以正常工作。

但是,如果有人提出解决 api_platform 所描述的 ES-Connector 问题的解决方案,请随时分享。