您的当前位置:首页>全部文章>文章详情

【PHP】PHP中使用ElasticSearch

CrazyPanda发表于:2024-07-26 14:32:09浏览:259次TAG: #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等。使用
发表于:2023-12-19 浏览:341 TAG:
【PHP】PHP8.1新特性大讲解之initializers初始化器
PHP 8.1:初始化器(new in initializers)PHP 8.1 添加了一个看似很小的细节,但我认为它会对许多人产生重大的日常影响。那么这个“初始化器 RFC 中的新内容”是关于什么的?我们来看一个例子;我们都写过这样的代码:class&nbsp;MyStateMachine { &nbsp;&nbsp;&nbsp;&nbsp;public&nbsp;function&nbsp;__construct( &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&amp;n
发表于:2024-01-04 浏览:320 TAG:
【PHP】什么是微服务架构
随着互联网的不断发展,越来越多的网站和应用程序应运而生。而对于开发者来说,如何快速高效地构建应用程序,是一个重要的挑战。其中,微服务架构已经成为了一个越来越受欢迎的解决方案。而php作为一种最受欢迎的web开发语言之一,也已经成为了很多开发者在构建微服务架构时的首选语言。本文将为大家介绍PHP如何应用于微服务架构,帮助大家更好地理解微服务架构以及如何使用PHP构建高效的微服务应用程序。什么是微服务架构?微服务架构(Microservices Architecture)是一种构建分布式应用程序的软
发表于:2024-05-23 浏览:297 TAG:
【PHP】支付宝第三方应用获取用户授权信息
支付宝服务商可创建第三方应用,一个第三方应用可绑定多个商家应用,商家应用绑定成功后会获取一个app_auth_token,授权令牌 app_auth_token 在没有重新授权、取消授权或刷新授权的情况下,永久有效。业务需要,要获取用户的user_id/open_id,1. 获取auth_code拼接授权urlhttps://openauth.alipay.com/oauth2/publicAppAuthorize.htm?app_id=APPID&amp;scope=auth_base&amp;am
发表于:2024-02-28 浏览:361 TAG:
【PHP】php删除数组中的重复值
随着互联网技术的快速发展,各种编程语言也在不断更新和发展。其中,PHP作为一门开发Web应用程序的强大语言,受到了广泛的关注和使用。在PHP编程中,数组是非常常用的数据类型之一,而处理数组中重复值的问题也是PHP开发人员经常遇到的问题之一。本文将介绍PHP中删除数组中重复值的方法。方法一:array_uniquePHP提供了一个内置函数array_unique(),可以用来删除数组中的重复值。array_unique()函数将返回一个新数组,该数组包含输入数组中所有的唯一值。使用arr
发表于:2023-12-19 浏览:323 TAG:
【PHP】TP6 Think-Swoole构建的RPC服务与微服务架构
引言:随着互联网的快速发展以及业务规模的扩大,传统的单体架构已经无法满足大规模业务场景的需求。因此,微服务架构应运而生。在微服务架构中,RPC(Remote Procedure Call)服务是实现服务间通信的一种重要方式。通过RPC服务,各个微服务之间可以方便、高效地互相调用。在本篇文章中,我们将介绍如何使用Think-Swoole框架构建RPC服务,实现微服务架构中的服务间通信,并提供具体的代码示例。一、TP6 Think-Swoole简介TP6 Think-Swoole是一个基于Think
发表于:2024-05-27 浏览:262 TAG:
【PHP】php判断字符串是否含有日文字符
可以使用PHP的正则表达式函数preg_match()来检测字符串上的日文字符。以下是一个例子:$str&nbsp;=&nbsp;&quot;こんにちは、世界!&quot;; if&nbsp;(preg_match(&#39;/\p{Hiragana}|\p{Katakana}|\p{Han}/u&#39;,&nbsp;$str))&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;echo&nbsp;&quot;字符串包含日文字符。&quot;; } &nbsp;else&amp;nbs
发表于:2023-12-13 浏览:307 TAG:
【PHP】PHP中的array_values()函数获取数组中的值
在PHP中,数组是一个十分常用且重要的数据类型。在实际开发过程中,我们经常需要操作数组中的值。其中,array_values()函数是一个非常有用的函数,它可以用于获取数组中的所有值并返回一个新的索引数组。array_values()函数的语法如下:array_values(array $array): array该函数需要一个数组作为参数,并返回一个新的索引数组,其中存储了原始数组中的所有值。下面是该函数的具体说明:参数$array:需要获取值的原始数组。返回值:一个新的索引数组,包含了原始数
发表于:2024-07-30 浏览:245 TAG:
【PHP】php哪些函数可以用来去幂方值
php可以用来去幂方值的函数有pow函数、双星号、exp函数、sqrt函数和log函数等。详细介绍:1、pow函数用于计算x的y次幂,x是底数,y是指数;2、双星号是幂运算符,用于计算一个数的幂;3、exp函数用于计算以e为底的x次幂,e是自然对数的底数,x是指数;4、sqrt函数用于计算一个数的平方根,x是计算平方根的数;5、log函数用于计算以指定底数为底的对数。本教程操作系统:windows10系统、PHP 8.1.3版本、DELL G3电脑。在PHP中,可以使用一些内置的函数来
发表于:2023-12-28 浏览:256 TAG:
【PHP】PHP8如何高效使用异步编程和代码
深入理解PHP8的新特性:如何高效使用异步编程和代码?PHP8是PHP编程语言的最新主要版本,带来了许多令人兴奋的新特性和改进。其中最突出的特性之一是对异步编程的支持。异步编程允许我们在处理并发任务时提高性能和响应能力。本文将深入探讨PHP8的异步编程特性,并介绍如何高效地使用它们。首先,让我们了解一下什么是异步编程。在传统的同步编程模型中,代码按照线性的顺序执行,一个任务必须等待另一个任务的完成才能继续执行。而在异步编程模型中,可以同时处理多个任务,不必等待其他任务的完成。这种并发执行的方式可
发表于:2024-01-08 浏览:406 TAG: