当尝试通过 fos_elastica-bundle (v6.0.0) 将 elasticsearch (v7.9.3) 实现到我的 Symfony (v5.3.10) - App with 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 使用此请求正文正确触发了对“/posts/_doc/_search”的 [POST] 请求:
{"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}
我确实从 elasticsearch 中得到了预期的结果:
#! 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 集成无法按预期工作并不断返回“未找到处理程序”错误消息?
我现在已经通过创建自定义 ApiResource - 过滤器来帮助自己
#[ApiFilter(FulltextFilter::class, arguments: ['index' => 'post'], properties: ['body','description','tag'])]
我的自定义过滤器实现了
ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\FilterInterface
,直接与ElasticSearch服务器通信,发送查询以搜索指定索引(帖子),并向aggregationBuilder添加另一个match()指令,其中包含一组与原始搜索匹配的ID:
<?php
declare(strict_types=1);
namespace App\Filter;
use ApiPlatform\Core\Bridge\Doctrine\MongoDbOdm\Filter\FilterInterface;
use Doctrine\ODM\MongoDB\Aggregation\Builder;
use Elastica\Result;
use Elastica\Client;
use Elastica\Query;
use Symfony\Component\PropertyInfo\Type;
/**
* Filter the collection by given properties.
*
*/
final class FulltextFilter implements FilterInterface
{
protected $index = '';
protected $properties = [];
protected $client;
protected $searchParameterName;
protected $maxResultsParameterName;
const DEFAULT_MAX_RESULTS = 200;
public function __construct(Client $client, string $index = '', string $maxResultsParameterName = 'amount', string $searchParameterName = 'query', array $properties = []) {
$this->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 问题,请随时分享。
问题是,FOS Elastica 需要带有结尾斜杠的 ES URL。但 Api 平台要求 URL 不以斜杠结尾。
我们通常在 .env 文件中定义 URL,然后在配置文件中调用它。 为了解决这个问题,我们可以在 .env 中定义不带斜杠的 URL,并将斜杠添加到 FOS Elastica 配置中。
# .env
###> friendsofsymfony/elastica-bundle ###
ELASTICSEARCH_URL=http://localhost:9200
###< friendsofsymfony/elastica-bundle ###
# config/packages/api_platform.yaml
api_platform:
elasticsearch:
enabled: true
hosts: [ '%env(ELASTICSEARCH_URL)%' ]
# config/packages/fos_elastica.yaml
fos_elastica:
clients:
default: { url: '%env(ELASTICSEARCH_URL)%/' }