【PHP】PHP中使用ElasticSearch
在es中,使用组合条件查询是其作为搜索引擎检索数据的一个强大之处,在前几篇中,简单演示了es的查询语法,但基本的增删改查功能并不能很好的满足复杂的查询场景,比如说我们期望像mysql那样做到拼接复杂的条件进行查询该如何做呢?es中有一种语法叫bool,通过在bool里面拼接es特定的语法可以做到大部分场景下复杂条件的拼接查询,也叫复合查询
首先简单介绍es中常用的组合查询用到的关键词,
filter:过滤,不参与打分
must:如果有多个条件,这些条件都必须满足 and与
should:如果有多个条件,满足一个或多个即可 or或
must_not:和must相反,必须都不满足条件才可以匹配到 !非
{
"bool": {
"must": [],--必须满足的条件--and
"should": [],--可以满足也可以不满足的条件--or
"must_not": []--不能满足的条件--not
}
}
发生 描述
must
该条款(查询)必须出现在匹配的文件,并将有助于得分。
filter
子句(查询)必须出现在匹配的文档中。然而不像 must查询的分数将被忽略。Filter子句在过滤器上下文中执行,这意味着评分被忽略,子句被考虑用于高速缓存。
should
子句(查询)应该出现在匹配的文档中。如果 bool查询位于查询上下文中并且具有mustor filter子句,则bool即使没有should查询匹配,文档也将匹配该查询 。在这种情况下,这些条款仅用于影响分数。如果bool查询是过滤器上下文 或者两者都不存在,must或者filter至少有一个should查询必须与文档相匹配才能与bool查询匹配。这种行为可以通过设置minimum_should_match参数来显式控制 。
must_not
子句(查询)不能出现在匹配的文档中。子句在过滤器上下文中执行,意味着评分被忽略,子句被考虑用于高速缓存。因为计分被忽略,0所有文件的分数被返回。
网上很多关于ES的例子都过时了,版本很久,这篇文章的测试环境是ES6.5
通过composer 安装
composer require 'elasticsearch/elasticsearch'
在代码中引入
require 'vendor/autoload.php';
use Elasticsearch\ClientBuilder;
// 配置
$client = ClientBuilder::create()->setHosts(['172.16.55.53'])->build();
// 配置
$client = ClientBuilder::create()->setHosts([
[
'host' => 'xxx',
'port' => '9200',
'scheme' => 'http',
'user' => 'xxx',
'pass' => 'xxxx'
],
])->build();
下面循序渐进完成一个简单的添加和搜索的功能。
首先要新建一个 index:
index 对应关系型数据(以下简称MySQL)里面的数据库,而不是对应MySQL里面的索引,这点要清楚
$params = [
'index' => 'myindex', #index的名字不能是大写和下划线开头
'body' => [
'settings' => [
'number_of_shards' => 2,
'number_of_replicas' => 0
]
]
];
$client->indices()->create($params);
在MySQL里面,光有了数据库还不行,还需要建立表,ES也是一样的,ES中的type对应MySQL里面的表。
注意:ES6以前,一个index有多个type,就像MySQL中一个数据库有多个表一样自然,但是ES6以后,每个index只允许一个type,在往以后的版本中很可能会取消type。
type不是单独定义的,而是和字段一起定义
$params = [
'index' => 'myindex',
'type' => 'mytype',
'body' => [
'mytype' => [
'_source' => [
'enabled' => true
],
'properties' => [
'id' => [
'type' => 'integer'
],
'first_name' => [
'type' => 'text',
'analyzer' => 'ik_max_word'
],
'last_name' => [
'type' => 'text',
'analyzer' => 'ik_max_word'
],
'age' => [
'type' => 'integer'
]
]
]
]
];
$client->indices()->putMapping($params);
在定义字段的时候,可以看出每个字段可以定义单独的类型,在first_name中还自定义了 分词器 ik,
这个分词器是一个插件,需要单独安装的,参考另一篇文章:ElasticSearch基本尝试
现在 数据库和表都有了,可以往里面插入数据了
概念:这里的 数据 在ES中叫 文档
$params = [
'index' => 'myindex',
'type' => 'mytype',
//'id' => 1, #可以手动指定id,也可以不指定随机生成
'body' => [
'first_name' => '张',
'last_name' => '三',
'age' => 35
]
];
$client->index($params);
多插入一点数据,然后来看看怎么把数据取出来:
通过id取出单条数据:
插曲:如果你之前添加文档的时候没有传入id,ES会随机生成一个id,这个时候怎么通过id查?id是多少都不知道啊。
所以这个插入一个简单的搜索,最简单的,一个搜索条件都不要,返回所有index下所有文档:
$data = $client->search();
现在可以去找一找id了,不过你会发现id可能长这样:zU65WWgBVD80YaV8iVMk,不要惊讶,这是ES随机生成的。
现在可以通过id查找指定文档了:
$params = [
'index' => 'myindex',
'type' => 'mytype',
'id' =>'zU65WWgBVD80YaV8iVMk'
];
$data = $client->get($params);
最后一个稍微麻烦点的功能:
注意:这个例子我不打算在此详细解释,看不懂没关系,这篇文章主要的目的是基本用法,并没有涉及到ES的精髓地方,
ES精髓的地方就在于搜索,后面的文章我会继续深入分析
$query = [
'query' => [
'bool' => [
'must' => [
'match' => [
'first_name' => '张',
]
],
'filter' => [
'range' => [
'age' => ['gt' => 76]
]
]
]
]
];
$params = [
'index' => 'myindex',
// 'index' => 'm*', #index 和 type 是可以模糊匹配的,甚至这两个参数都是可选的
'type' => 'mytype',
'_source' => ['first_name','age'], // 请求指定的字段
'body' => array_merge([
'from' => 0,
'size' => 5
],$query)
];
$data = $this->EsClient->search($params);
上面的是一个简单的使用流程,但是不够完整,只讲了添加文档,没有说怎么删除文档,
下面我贴出完整的测试代码,基于Laravel环境,当然环境只影响运行,不影响理解,包含基本的常用操作:
<?php
use Elasticsearch\ClientBuilder;
use Elasticsearch\Common\Exceptions\Missing404Exception;
use services\CommonService;
use yii\base\UserException;
use yii\db\Exception;
class BaseEs
{
/**
* @var $instance Es类实例
*/
private static $instance;
/**
* @var $debug 设置debug
*/
protected $debug;
/**
* 客户端
* @var \Elasticsearch\Client
*/
private $client;
/**
* @var 索引名称
*/
protected $index;
/**
* @var 索引别名
*/
public $alias;
protected $retries = 2;
// 外网
private $config = [];
// 内网
private $lanConfig = [];
/**
* 查询参数
* @var array $queryParams
*/
protected $queryParams = [];
private function __construct()
{
if (DEBUG) {
$this->config = [
'xxx:9200',
];
$this->lanConfig = [
'xxx:9200',
];
} else {
$this->config = [
[
'host' => 'xxx',
'port' => '9200',
'scheme' => 'http',
'user' => 'xxx',
'pass' => 'xxx',
],
];
$this->lanConfig = [
[
'host' => 'xxx',
'port' => '9200',
'scheme' => 'http',
'user' => 'xxx',
'pass' => 'xxx',
],
];
}
try {
$ip = CommonService::getIp();
if (strpos($ip, '222.222') !== false) {
$config = $this->lanConfig;
} else {
$config = $this->config;
}
if (!$this->client instanceof Elasticsearch\Client) {
$this->client = ClientBuilder::create()
->setHosts($config)
->setRetries($this->retries)
->build();
}
} catch (\Exception $e) {
throw new Exception('Es conn failed');
}
}
public function select($params = '')
{
$this->queryParams['select'] = str_replace(' ', '', trim($params, " ,\t\r\n"));
return $this;
}
public function from($number = 0)
{
$this->queryParams['from'] = $number ? ($number - 1) * 10 : 0;
return $this;
}
public function size($number = 10)
{
$this->queryParams['size'] = $number;
return $this;
}
public function equal($params = [])
{
$this->queryParams['equal'][] = $params;
return $this;
}
public function in($params = [])
{
$this->queryParams['in'][] = $params;
return $this;
}
public function like($params = [])
{
$this->queryParams['like'][] = $params;
return $this;
}
public function orlike($params = [])
{
$this->queryParams['orlike'][] = $params;
return $this;
}
public function order($params = [])
{
$this->queryParams['order'] = $params;
return $this;
}
/**
* 根据条件删除文档
*
* @param array $params
* @return array
*/
public function deleteDoc($params = [])
{
return $this->client->deleteByQuery($params);
}
/**
* 根据id删除文档
*
* @param int $id
* @return array
*/
public function deleteDocById($id = 0)
{
$params = [
'index' => $this->index,
'id' => $id,
];
$response = $this->client->delete($params);
return $response;
}
/**
* 组装查询条件
*
* @param array $params
* @return $this
*/
private function parseQueryParams()
{
$queryParams = [
'index' => $this->index,
'body' => [
'query' => [
'bool' => [
'must' => [],
'filter' => [],
],
],
],
'from' => $this->queryParams['from'] ?? 0,
'size' => $this->queryParams['size'] ?? 10,
];
$filter = $must = [];
if (!empty($this->queryParams['select'])) {
$queryParams['_source'] = explode(',', $this->queryParams['select']);
}
if (!empty($this->queryParams['equal'])) {
foreach ($this->queryParams['equal'] as $key => $row) {
foreach ($row as $filed => $value) {
$filter[] = [
'term' => [$filed => $value],
];
}
}
}
if (!empty($this->queryParams['in'])) {
foreach ($this->queryParams['in'] as $key => $row) {
foreach ($row as $filed => $value) {
$filter[] = [
'terms' => [$filed => array_values(array_unique(array_filter($value)))],
];
}
}
}
if (!empty($this->queryParams['like'])) {
foreach ($this->queryParams['like'] as $key => $row) {
foreach ($row as $filed => $value) {
/*$must[] = [
'wildcard' => [$filed => '*'. $value. '*']
];*/
$must[] = [
// 'match' => [$filed => $value]
'match_phrase' => [$filed => $value],
];
}
}
$queryParams['body']['query']['bool']['must'] = $must;
}
if (!empty($this->queryParams['orlike'])) {
foreach ($this->queryParams['orlike'] as $key => $row) {
foreach ($row as $filed => $value) {
$orlike[] = [
'match_phrase' => [$filed => $value],
];
}
}
$queryParams['body']['query']['bool']['must']['bool']['should'] = $orlike;
}
if (!empty($this->queryParams['order'])) {
$queryParams['body']['sort'] = [
key($this->queryParams['order']) => [
'order' => current($this->queryParams['order']),
],
];
}
$queryParams['body']['query']['bool']['filter'] = $filter;
$this->queryParams = $queryParams;
return $this;
}
/**
* @param bool $isTotal isTotal=true时, 返回总数
* @return array|string
*/
public function query($isTotal = false)
{
try {
$this->parseQueryParams();
if ($this->debug) {
return \GuzzleHttp\json_encode($this->queryParams);
}
if ($isTotal === true) {
unset(
$this->queryParams['from'],
$this->queryParams['size'],
$this->queryParams['_source']
);
$count = $this->client->count($this->queryParams);
return (int)$count['count'];
}
if (!empty($this->queryParams)) {
$result = $this->client->search($this->queryParams);
if (!empty($result['hits']['hits'])) {
$return = [];
foreach ($result['hits']['hits'] as $row) {
$return[] = $row['_source'];
}
return $return;
} else {
return [];
}
}
} catch (\Exception $e) {
$msg = $e->getMessage();
$msg = '服务器开小差了~';
throw new Exception($msg);
}
}
/**
* 创建文档
*
* @param array $body
* @return array|bool
*/
public function createDocument($body = [], $id = null)
{
try {
if (!empty($body)) {
$params = [
'index' => $this->index,
'body' => $body,
];
if ($id) {
$params['id'] = $id;
}
$column = $params['body'];
$indexMappings = $this->mappings();
$diff = array_diff(array_keys($column), array_keys($indexMappings));
if (!empty($diff)) {
throw new Exception('different from the default mappings', $diff);
}
return $this->client->index($params);
}
} catch (\Exception $e) {
throw $e;
}
}
public function getIndex()
{
return $this->index;
}
/**
* @return BaseEs|Es类实例
* @throws Exception
*/
public static function setEs($index = '')
{
$class = get_called_class();
if (!self::$instance || !self::$instance instanceof $class) {
self::$instance = new static();
}
if ($index) {
self::$instance->index = $index;
}
return self::$instance;
}
protected function settings()
{
return [
'number_of_shards' => 1,
'number_of_replicas' => 0,
];
}
/**
* @return bool
* @throws Exception
*/
public function createIndex()
{
try {
$params = [
'index' => $this->index,
];
$check = $this->indices()->exists($params);
if ($check) {
throw new Exception('index: ' . $this->index . ' already exists');
}
$params = [
'index' => $this->index,
'body' => [
'settings' => $this->settings(),
'mappings' => [
'_source' => [
'enabled' => true,
],
'properties' => $this->mappings(),
],
],
];
$result = $this->indices()->create($params);
return $result['acknowledged'] === true;
} catch (\Exception $e) {
throw new Exception($e->getMessage());
}
}
/**
* 删除索引
*
* @return bool
* @throws Missing404Exception
*/
public function deleteIndex()
{
try {
$params = [
'index' => $this->index,
];
$check = $this->indices()->exists($params);
if (!$check) {
return true;
}
$result = $this->indices()->delete([
'index' => $this->index,
]);
return $result['acknowledged'] === true;
} catch (Missing404Exception $e) {
throw new Missing404Exception('no such index ' . $this->index);
}
}
/**
* 批量写入
* type 1.增加/修改 2.删除
* $data = [
* ['id' => 1, 'name' => 'llf', 'age' => 30],
* ['id' => 1, 'name' => 'llf', 'age' => 30],
* ['id' => 1, 'name' => 'llf', 'age' => 30],
* ['id' => 1, 'name' => 'llf', 'age' => 30],
* ];
* @param array $data
*/
public function doBulkDocument($type = 1, $data = [])
{
try {
$params = ['body' => []];
if ($type == 1) {
foreach ($data as $key => $row) {
$params['body'][] = [
'index' => [
'_index' => $this->index,
'_id' => $row['info_id'] . '-' . $row['info_type'],
],
];
$params['body'][] = $row;
if (($key + 1) % 10000 == 0) {
$this->client->bulk($params);
$params = ['body' => []];
}
}
} else {
foreach ($data as $key => $row) {
$params['body'][] = [
'delete' => [
'_index' => $this->index,
'_id' => $row['info_id'] . '-' . $row['info_type'],
],
];
}
}
if (!empty($params['body'])) {
$this->client->bulk($params);
return true;
}
} catch (\Exception $e) {
throw new Exception($e->getMessage());
}
}
/**
* @return array @todo
*/
public function updateIndex()
{
$putParams = [
'index' => $this->index,
//'type' => '_doc',
'body' => [
'properties' => $this->mappings(),
],
];
return $this->indices()->putMapping($putParams);
}
/**
* 方便调试, 直接返回拼接的query
* @return $this
*/
public function setDebug()
{
$this->debug = true;
return $this;
}
private function indices()
{
return $this->client->indices();
}
public function analyze($text = '')
{
$params = [
'index' => $this->index,
'body' => [
'analyzer' => 'ik_smart',
'text' => $text,
],
];
return $this->indices()->analyze($params);
}
public function update($id = 0, $updateParams = [])
{
$params = [
'index' => $this->index,
'id' => $id,
'body' => [
'doc' => $updateParams,
],
];
return $this->client->update($params);
}
}
猜你喜欢
- 【PHP】在vscode中要用php需安装什么
- 在Vscode中使用Php需安装什么?随着Php的使用范围逐渐扩大,越来越多的人开始在Vscode中使用Php进行开发。但是,要在Vscode中使用Php需要安装一些必要的扩展和插件。本篇文章将为大家讲解在Vscode中使用Php需要安装的扩展和插件。PHP Extension PackPHP Extension Pack是由Microsoft开发的一个扩展包,其中包括了一些必要的Php扩展,比如Php Debug、Php IntelliSense、Php DocBlocker等。使用
- 【PHP】PHP8.1新特性大讲解之initializers初始化器
- PHP 8.1:初始化器(new in initializers)PHP 8.1 添加了一个看似很小的细节,但我认为它会对许多人产生重大的日常影响。那么这个“初始化器 RFC 中的新内容”是关于什么的?我们来看一个例子;我们都写过这样的代码:class MyStateMachine { public function __construct( &n
- 【PHP】什么是微服务架构
- 随着互联网的不断发展,越来越多的网站和应用程序应运而生。而对于开发者来说,如何快速高效地构建应用程序,是一个重要的挑战。其中,微服务架构已经成为了一个越来越受欢迎的解决方案。而php作为一种最受欢迎的web开发语言之一,也已经成为了很多开发者在构建微服务架构时的首选语言。本文将为大家介绍PHP如何应用于微服务架构,帮助大家更好地理解微服务架构以及如何使用PHP构建高效的微服务应用程序。什么是微服务架构?微服务架构(Microservices Architecture)是一种构建分布式应用程序的软
- 【PHP】支付宝第三方应用获取用户授权信息
- 支付宝服务商可创建第三方应用,一个第三方应用可绑定多个商家应用,商家应用绑定成功后会获取一个app_auth_token,授权令牌 app_auth_token 在没有重新授权、取消授权或刷新授权的情况下,永久有效。业务需要,要获取用户的user_id/open_id,1. 获取auth_code拼接授权urlhttps://openauth.alipay.com/oauth2/publicAppAuthorize.htm?app_id=APPID&scope=auth_base&am
- 【PHP】php删除数组中的重复值
- 随着互联网技术的快速发展,各种编程语言也在不断更新和发展。其中,PHP作为一门开发Web应用程序的强大语言,受到了广泛的关注和使用。在PHP编程中,数组是非常常用的数据类型之一,而处理数组中重复值的问题也是PHP开发人员经常遇到的问题之一。本文将介绍PHP中删除数组中重复值的方法。方法一:array_uniquePHP提供了一个内置函数array_unique(),可以用来删除数组中的重复值。array_unique()函数将返回一个新数组,该数组包含输入数组中所有的唯一值。使用arr
- 【PHP】TP6 Think-Swoole构建的RPC服务与微服务架构
- 引言:随着互联网的快速发展以及业务规模的扩大,传统的单体架构已经无法满足大规模业务场景的需求。因此,微服务架构应运而生。在微服务架构中,RPC(Remote Procedure Call)服务是实现服务间通信的一种重要方式。通过RPC服务,各个微服务之间可以方便、高效地互相调用。在本篇文章中,我们将介绍如何使用Think-Swoole框架构建RPC服务,实现微服务架构中的服务间通信,并提供具体的代码示例。一、TP6 Think-Swoole简介TP6 Think-Swoole是一个基于Think
- 【PHP】php判断字符串是否含有日文字符
- 可以使用PHP的正则表达式函数preg_match()来检测字符串上的日文字符。以下是一个例子:$str = "こんにちは、世界!"; if (preg_match('/\p{Hiragana}|\p{Katakana}|\p{Han}/u', $str)) { echo "字符串包含日文字符。"; } else&nbs
- 【PHP】PHP中的array_values()函数获取数组中的值
- 在PHP中,数组是一个十分常用且重要的数据类型。在实际开发过程中,我们经常需要操作数组中的值。其中,array_values()函数是一个非常有用的函数,它可以用于获取数组中的所有值并返回一个新的索引数组。array_values()函数的语法如下:array_values(array $array): array该函数需要一个数组作为参数,并返回一个新的索引数组,其中存储了原始数组中的所有值。下面是该函数的具体说明:参数$array:需要获取值的原始数组。返回值:一个新的索引数组,包含了原始数