通过PHP简单使用Elasticsearch
<h3> 说明</h3><p> 本文记录了如何使用 PHP 简单操作 Elasticsearch(ES),适合对 ES 没有一定了解的读者。本文使用本地 Docker 环境进行开发,并提供了示例代码。</p>
<p>示例代码已上传至 码云,欢迎查看。</p>
<h3>搭建开发环境</h3>
<p>为了方便读者,本文将使用 Docker 搭建开发环境,并以 PHP 7.2 为例。通过本文的指导,读者可以轻松地完成环境搭建,进而进行 Elasticsearch 相关的开发工作。</p>
<p>第一步拉取镜像文件:</p>
<p>拉取 Elasticsearch 镜像,这一步需要先在机器中安装好 docker 并在终端中输入以下命令:</p>
<div class="cnblogs_code">
<pre>docker pull elasticsearch:<span style="color: rgba(128, 0, 128, 1)">7.7</span>.<span style="color: rgba(128, 0, 128, 1)">1</span></pre>
</div>
<p>在执行完拉取镜像命令后可能会报错或者是下载速度过慢的情况,可以在网上查找切换 docker 源来解决。</p>
<p>同样在终端拉取 Kibana 镜像:</p>
<div class="cnblogs_code">
<pre>docker pull kibana:<span style="color: rgba(128, 0, 128, 1)">7.7</span>.<span style="color: rgba(128, 0, 128, 1)">1</span></pre>
</div>
<div class="group w-full text-gray-800 dark:text-gray-100 border-b border-black/10 dark:border-gray-900/50 bg-gray-50 dark:bg-[#444654]">
<div class="text-base gap-4 md:gap-6 md:max-w-2xl lg:max-w-2xl xl:max-w-3xl p-4 md:py-6 flex lg:px-0 m-auto">
<div class="relative flex w- flex-col gap-1 md:gap-3 lg:w-">
<div class="flex flex-grow flex-col gap-3">
<div class="min-h- flex flex-col items-start gap-4 whitespace-pre-wrap">
<div class="markdown prose w-full break-words dark:prose-invert light">
<p>拉取好镜像后,您可以通过实例化容器来运行它们,使得 Elasticsearch 和 Kibana 服务可以运行起来并且相互连接。</p>
<p>首先您需要启动 Elasticsearch 容器,并且将本地目录 /Users/fangaolin/www/es_plugin 映射到容器目录 /www/es_plugin 上。请注意,/Users/fangaolin/www/es_plugin 是本地电脑上的目录,您需要将其替换为您自己的目录。</p>
<p>这个目录是用来存放我们的 Elasticsearch 插件用的。</p>
<div class="cnblogs_code">
<pre>docker run -d --name es -p <span style="color: rgba(128, 0, 128, 1)">9200</span>:<span style="color: rgba(128, 0, 128, 1)">9200</span> -p <span style="color: rgba(128, 0, 128, 1)">9300</span>:<span style="color: rgba(128, 0, 128, 1)">9300</span> -v <strong>/Users/fangaolin/www/es_plugin</strong>:/www/es_plugin -e <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">discovery.type=single-node</span><span style="color: rgba(128, 0, 0, 1)">"</span> -e <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ES_JAVA_OPTS=-Xms512m -Xmx512m</span><span style="color: rgba(128, 0, 0, 1)">"</span> elasticsearch:<span style="color: rgba(128, 0, 128, 1)">7.7</span>.<span style="color: rgba(128, 0, 128, 1)">1</span></pre>
</div>
<p>启动 Kibana 容器,并指定 Elasticsearch 服务地址为 http://host.docker.internal:9200/ 这样能让我们的 Kibana 访问 Elasticsearch。</p>
<div class="cnblogs_code">
<pre>docker run -d --name kibana -e ELASTICSEARCH_HOSTS=http:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">host.docker.internal:9200/ -p 5601:5601 kibana:7.7.1</span></pre>
</div>
<div class="group w-full text-gray-800 dark:text-gray-100 border-b border-black/10 dark:border-gray-900/50 bg-gray-50 dark:bg-[#444654]">
<div class="text-base gap-4 md:gap-6 md:max-w-2xl lg:max-w-2xl xl:max-w-3xl p-4 md:py-6 flex lg:px-0 m-auto">
<div class="relative flex w- flex-col gap-1 md:gap-3 lg:w-">
<div class="flex flex-grow flex-col gap-3">
<div class="min-h- flex flex-col items-start gap-4 whitespace-pre-wrap">
<div class="markdown prose w-full break-words dark:prose-invert light">
<p>当 Elasticsearch 和 Kibana 服务启动之后,我们需要安装分词扩展。Elasticsearch 自带的默认分词扩展对中文的处理效果不是很好,因此我们需要安装 IK Analyzer 扩展来提高中文分词的处理效果。</p>
</div>
</div>
</div>
</div>
</div>
</div>
<p>首先进入刚刚启动的 Elasticsearch 容器,在终端输入:</p>
<div class="cnblogs_code">
<pre>docker exec -it es /bin/<span style="color: rgba(0, 0, 255, 1)">sh</span></pre>
</div>
<p>这是一个在 Docker 容器中执行命令的命令,其中:</p>
<ul>
<li>docker exec 是执行 Docker 容器中的命令的命令;</li>
<li>-it 是告诉 Docker,让命令在一个交互式的终端里执行;</li>
<li>es 是容器的名称或 ID;</li>
<li>/bin/sh 是在容器中要执行的命令。</li>
</ul>
<p>该命令将启动一个新的终端会话,该会话与名为 es 的容器关联,该终端会话将运行一个交互式的 shell(/bin/sh),并在其中执行命令。</p>
<p>执行如下命令开始安装扩展:</p>
<div class="cnblogs_code">
<pre>./bin/elasticsearch-plugin <span style="color: rgba(0, 0, 255, 1)">install</span> https:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.7.1/elasticsearch-analysis-ik-7.7.1.zip</span></pre>
</div>
<p>同时需要新建一个同义词的文件,这个文件是用来管理我们的同义词。这里我们用 vi 来新建一个文件。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">vi</span> /usr/share/elasticsearch/config/synonyms.txt</pre>
</div>
<div class="group w-full text-gray-800 dark:text-gray-100 border-b border-black/10 dark:border-gray-900/50 bg-gray-50 dark:bg-[#444654]">
<div class="text-base gap-4 md:gap-6 md:max-w-2xl lg:max-w-2xl xl:max-w-3xl p-4 md:py-6 flex lg:px-0 m-auto">
<div class="relative flex w- flex-col gap-1 md:gap-3 lg:w-">
<div class="flex flex-grow flex-col gap-3">
<div class="min-h- flex flex-col items-start gap-4 whitespace-pre-wrap">
<div class="markdown prose w-full break-words dark:prose-invert light">
<p>进入 vi 编辑器后,您可以通过输入 :wq 命令来保存并退出。如果您尝试使用该命令却没有反应,可能需要您检查一下您的输入法是否处于英文状态。另外,您可以直接输入 i 进入编辑模式,然后保存并退出编辑器。</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="cnblogs_code">
<pre>:wq</pre>
</div>
<div class="group w-full text-gray-800 dark:text-gray-100 border-b border-black/10 dark:border-gray-900/50 bg-gray-50 dark:bg-[#444654]">
<div class="text-base gap-4 md:gap-6 md:max-w-2xl lg:max-w-2xl xl:max-w-3xl p-4 md:py-6 flex lg:px-0 m-auto">
<div class="relative flex w- flex-col gap-1 md:gap-3 lg:w-">
<div class="flex flex-grow flex-col gap-3">
<div class="min-h- flex flex-col items-start gap-4 whitespace-pre-wrap">
<div class="markdown prose w-full break-words dark:prose-invert light">
<p>完成以上步骤后,您已经成功安装了 IK Analyzer 扩展。您可以使用 exit 命令退出容器并返回到宿主机。</p>
<div class="cnblogs_code">
<pre>exit</pre>
</div>
<p>之后我们再次开始练习就可以直接启动容器就可以,启动容器时使用 docker start 命令,如: docker start es 同时这里也列举了常见的 docker 命令:</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="cnblogs_code"><ol>
<li>启动 Docker 容器:<code>docker start <container_name></code></li>
<li>停止 Docker 容器:<code>docker stop <container_name></code></li>
<li>重启 Docker 容器:<code>docker restart <container_name></code></li>
<li>查看 Docker 容器状态:<code>docker ps</code> 或 <code>docker container ls</code></li>
<li>查看所有 Docker 容器:<code>docker ps -a</code> 或 <code>docker container ls -a</code></li>
<li>进入 Docker 容器:<code>docker exec -it <container_name> /bin/bash</code></li>
<li>删除 Docker 容器:<code>docker rm <container_name></code> 或 <code>docker container rm <container_name></code></li>
<li>删除 Docker 镜像:<code>docker rmi <image_name></code> 或 <code>docker image rm <image_name></code></li>
<li>构建 Docker 镜像:<code>docker build -t <image_name> .</code></li>
<li>拉取 Docker 镜像:<code>docker pull <image_name></code></li>
<li>推送 Docker 镜像:<code>docker push <image_name></code></li>
</ol></div>
<p><span style="font-size: 15px">此时你可以在浏览器中访问 http://localhost:5601/ 来进入 Kibana 后台,以及访问 http://localhost:9200/ 来查看 Elasticsearch 的信息。请注意,这两个服务启动需要一定的时间,如果没有立即看到结果,请多刷新几次。需要注意的是,我们实际生成的信息都存储在 Elasticsearch 中,而 Kibana 是一个可视化工具,可以帮助我们更方便地查看和分析这些数据,类似于 Navicat 和 MySQL 的关系。</span></p>
<h3>初始化 PHP 环境</h3>
<div class="group w-full text-gray-800 dark:text-gray-100 border-b border-black/10 dark:border-gray-900/50 bg-gray-50 dark:bg-[#444654]">
<div class="text-base gap-4 md:gap-6 md:max-w-2xl lg:max-w-2xl xl:max-w-3xl p-4 md:py-6 flex lg:px-0 m-auto">
<div class="relative flex w- flex-col gap-1 md:gap-3 lg:w-">
<div class="flex flex-grow flex-col gap-3">
<div class="min-h- flex flex-col items-start gap-4 whitespace-pre-wrap">
<div class="markdown prose w-full break-words dark:prose-invert light">
<p>开始编写 PHP 代码,可以在本地新建一个目录并通过终端进入该目录进行操作。</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 初始化项目的composer 一路默认即可</span>
<span style="color: rgba(0, 0, 0, 1)"> composer init
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 引入包</span>
composer require elasticsearch/elasticsearch <span style="color: rgba(128, 0, 128, 1)">7.11</span>.<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">
composer require ruflin</span>/elastica <span style="color: rgba(128, 0, 128, 1)">7.1</span></pre>
</div>
<p><span style="font-size: 15px">首先需要用上 PHP elasticsearch/elasticsearch 这个包,安装时要注意和 Elasticsearch 的版本相对应;</span></p>
<p><span style="font-size: 15px">如果直接用我上传在码云的项目只需在项目根目录下执行 composer install 即可</span></p>
<p><span style="font-size: 15px">如果你想自己新建可以执行下面命令</span></p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 初始化项目的composer 一路默认即可</span>
<span style="color: rgba(0, 0, 0, 1)"> composer init
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 执行命令</span>
composer require elasticsearch/elasticsearch <span style="color: rgba(128, 0, 128, 1)">7.11</span>.<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">
composer require ruflin</span>/elastica <span style="color: rgba(128, 0, 128, 1)">7.1</span></pre>
</div>
<p><span style="font-size: 15px">关于PHP API的使用,ES 官网的文档库中有一份 中文文档</span></p>
<p><span style="font-size: 15px">实例化客户端PHP代码:</span> </p>
<div class="cnblogs_code">
<pre> $builder =<span style="color: rgba(0, 0, 0, 1)"> ClientBuilder::create();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 连接地址</span>
$hosts = [<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">localhost</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">];
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置es地址</span>
$builder-><span style="color: rgba(0, 0, 0, 1)">setHosts($hosts);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 还可以设置记录日志的实例,但要实现 LoggerInterface 接口的方法。
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> $builder->setLogger();</span>
<span style="color: rgba(0, 0, 0, 1)">
$client </span>= $builder->build();</pre>
</div>
<p><span style="font-size: 15px">后面的操作都将基于这个实例完成,所以可以把这个步骤用单例封装一下,如果是在框架里使用可以考虑用框架中容器工具封装下。</span></p>
<p><span style="font-size: 15px">除此之外还把配置信息单独抽离了出来:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ElasticsearchObj
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)"> $singleObj;
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> $client;
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> function __construct()
{
$</span><span style="color: rgba(0, 0, 255, 1)">this</span>->client = $<span style="color: rgba(0, 0, 255, 1)">this</span>-><span style="color: rgba(0, 0, 0, 1)">init();
</span><span style="color: rgba(0, 0, 255, 1)">return</span> $<span style="color: rgba(0, 0, 255, 1)">this</span>-><span style="color: rgba(0, 0, 0, 1)">client;
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 初始化es连接实例
*
* @return Client
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> function init()
{
$builder </span>=<span style="color: rgba(0, 0, 0, 1)"> ClientBuilder::create();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 连接地址</span>
$hosts = Config::getConfig(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hosts</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置es地址</span>
$builder-><span style="color: rgba(0, 0, 0, 1)">setHosts($hosts);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 还可以设置记录日志的实例,但要完成 LoggerInterface 接口的方法。
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> $builder->setLogger();</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> $builder-><span style="color: rgba(0, 0, 0, 1)">build();
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 获得单例对象
*
* @return ElasticsearchObj
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)"> function getInstance()
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (isset(self::$singleObj)) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> self::$singleObj;
}
self::$singleObj </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ElasticsearchObj();
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> self::$singleObj;
}
}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Config {
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> $config =<span style="color: rgba(0, 0, 0, 1)"> [
</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hosts</span><span style="color: rgba(128, 0, 0, 1)">'</span> =><span style="color: rgba(0, 0, 0, 1)"> [
</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">127.0.0.1</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
]
];
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)"> function getConfig($name)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (isset(self::$config[$name])){
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> self::$config[$name];
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">''</span><span style="color: rgba(0, 0, 0, 1)">;
}
}</span></pre>
</div>
<h3>快速添加数据 & Kibana 查看数据</h3>
<p><span style="font-size: 15px">ES 一般默认是打开 Dynamic Mapping 的,即 ES 在插入时没有 mapping 时会自己推算类型,创造一个 mapping 让文档插入成功。</span></p>
<p><span style="font-size: 15px">可以先写一些简单的 demo 尝试往 ES 中写一些数据:</span></p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 通过直接插入数据,生成一条全新的index</span>
$docItem =<span style="color: rgba(0, 0, 0, 1)"> [
</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">id</span><span style="color: rgba(128, 0, 0, 1)">'</span> => <span style="color: rgba(128, 0, 128, 1)">10</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">name</span><span style="color: rgba(128, 0, 0, 1)">'</span> => <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">红富士苹果</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">price</span><span style="color: rgba(128, 0, 0, 1)">'</span> => <span style="color: rgba(128, 0, 128, 1)">19.9</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">category</span><span style="color: rgba(128, 0, 0, 1)">'</span> => <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">fruit</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
];
$indexName </span>= <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">new_index</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
$</span><span style="color: rgba(0, 0, 255, 1)">params</span> =<span style="color: rgba(0, 0, 0, 1)"> [
</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">index</span><span style="color: rgba(128, 0, 0, 1)">'</span> =><span style="color: rgba(0, 0, 0, 1)"> $indexName,
</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">id</span><span style="color: rgba(128, 0, 0, 1)">'</span> => $docItem[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">id</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">],
</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">body</span><span style="color: rgba(128, 0, 0, 1)">'</span>=><span style="color: rgba(0, 0, 0, 1)"> $docItem
];
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 是不是很简单 主要是构造一些参数</span>
$client->index($<span style="color: rgba(0, 0, 255, 1)">params</span>);</pre>
</div>
<p><span style="font-size: 15px">同样可以对插入操作进行封装并放在 ES 对象中:</span></p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 插入文档
*
* @param string $indexName
* @param int $id
* @param array $insertData
* @return array|callable
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> insert(<span style="color: rgba(0, 0, 255, 1)">string</span> <span style="color: rgba(128, 0, 128, 1)">$indexName</span>, int <span style="color: rgba(128, 0, 128, 1)">$id</span>, <span style="color: rgba(0, 0, 255, 1)">array</span> <span style="color: rgba(128, 0, 128, 1)">$insertData</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(128, 0, 128, 1)">$params</span> =<span style="color: rgba(0, 0, 0, 1)"> [
</span>'index' => <span style="color: rgba(128, 0, 128, 1)">$indexName</span>,
'id' => <span style="color: rgba(128, 0, 128, 1)">$id</span>,
'body'=> <span style="color: rgba(128, 0, 128, 1)">$insertData</span><span style="color: rgba(0, 0, 0, 1)">
];
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$this</span>->client->index(<span style="color: rgba(128, 0, 128, 1)">$params</span><span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p><span style="font-size: 15px">封装后就可以通过面向对象的方式调用,即数据和操作相分离:</span></p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(128, 0, 128, 1)">$client</span> = ElasticsearchObj::<span style="color: rgba(0, 0, 0, 1)">getInstance();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 通过直接插入数据,生成一条全新的index</span>
<span style="color: rgba(128, 0, 128, 1)">$docItem</span> =<span style="color: rgba(0, 0, 0, 1)"> [
</span>'id' => 10,
'name' => '红富士苹果',
'price' => 19.9,
'category' => 'fruit'<span style="color: rgba(0, 0, 0, 1)">
];
</span><span style="color: rgba(128, 0, 128, 1)">$indexName</span> = 'new_index'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$client</span>->insert(<span style="color: rgba(128, 0, 128, 1)">$indexName</span>, <span style="color: rgba(128, 0, 128, 1)">$docItem</span>['id'], <span style="color: rgba(128, 0, 128, 1)">$docItem</span>);</pre>
</div>
<p><span style="font-size: 15px">直接在 src 目录下执行 php index.php 即可。</span></p>
<p><span style="font-size: 15px">如果没有报错的话,现在通过配置一下 Kibana 就可以看到刚刚添加的数据。</span></p>
<p><span style="font-size: 15px"><img src="https://img2022.cnblogs.com/blog/1340345/202203/1340345-20220302144436497-724382290.png"></span></p>
<p> </p>
<p> </p>
<p><span style="font-size: 15px"><img src="https://img2022.cnblogs.com/blog/1340345/202203/1340345-20220302144519118-183051147.png"></span></p>
<p> </p>
<p> </p>
<p><span style="font-size: 15px"><img src="https://img2022.cnblogs.com/blog/1340345/202203/1340345-20220302144556525-53111644.png"></span></p>
<h3>Mappings</h3>
<p><span style="font-size: 15px">Mapping 类似与数据库中表的定义,指定了字段的类型与其它信息。</span></p>
<p><span style="font-size: 15px">但至此并没有设置任何 Mapping。</span></p>
<p><span style="font-size: 15px">前面说过 ES 会默认推算字段类型,并且可以在 Kibana 上查看到。</span></p>
<p> <img src="https://img2022.cnblogs.com/blog/1340345/202203/1340345-20220302144949464-1013578583.png"></p>
<p> </p>
<p><span style="font-size: 15px">为了方便快捷,可以参考自动生成的 Mapping,在这个基础上修改字段类型,至于有哪些类型可以网上查一下;</span></p>
<p><span style="font-size: 15px">不仅需要知道字段有哪些类型还需要知道 tokenizers & analyzer & filter 三者的区别:</span> </p>
<h4>Tokenizers 分词器</h4>
<p> <span style="font-size: 15px">分词器可以按照我们的设定将文本进行拆分,打散。</span> </p>
<h4>Token Filters 字符过滤器</h4>
<p><span style="font-size: 15px">前者打散后的字符称为 token,token filters 即进一步过滤,比如统一转大写,转小写。</span></p>
<h4>Analyzer 分析器</h4>
<p><span style="font-size: 15px">即分词器与字符过滤器的组合,通过分析器可以应用在 elasticsearch 字段上;</span></p>
<p><span style="font-size: 15px">elasticsearch 默认自带了很多的分析器但是对中文的拆分都不是很好,前面安装的ik对中文支持就非常好。</span></p>
<p><span style="font-size: 15px">通过 Kibana 可以测试分析器对文本应用的效果:</span></p>
<p><span style="font-size: 15px"><img src="https://img2022.cnblogs.com/blog/1340345/202203/1340345-20220302145436687-139252597.png"></span></p>
<p> </p>
<p> </p>
<p><span style="font-size: 15px"><img src="https://img2022.cnblogs.com/blog/1340345/202203/1340345-20220302145505916-22731379.png"><span style="font-size: 15px">详细的内容还可以看下 官方文档</span></span></p>
<p><span style="font-size: 15px">知道了这些概念后就可以回归代码了,对于 ES 的每个索引来说就和 MySQL 中的表一样。</span></p>
<p><span style="font-size: 15px">为了能合理存放这些索引属性信息,将每个索引信息分别对应存放在一个对象实例中并通过接口约束实例的方法。</span></p>
<p><span style="font-size: 15px">后面使用时只需面向接口编程,不用考虑实际用了哪个索引。</span></p>
<p><span style="font-size: 15px">说了这么多,直接看代码吧:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 新建接口</span>
<span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> IndexInterface
{
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 获取索引名称
*
* @return mixed
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> getIndexName(): <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 获取属性信息
*
* @return mixed
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> getProperties(): <span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 获取索引上的分词设置
*
* @return mixed
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> getSettings(): <span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 实现接口填充接口方法</span>
<span style="color: rgba(0, 0, 255, 1)">class</span> ItemsIndex <span style="color: rgba(0, 0, 255, 1)">implements</span><span style="color: rgba(0, 0, 0, 1)"> IndexInterface
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(128, 0, 128, 1)">$name</span> = 'new_index'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 前面说到的分词设置</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(128, 0, 128, 1)">$settings</span> =<span style="color: rgba(0, 0, 0, 1)"> [
</span>'analysis' =><span style="color: rgba(0, 0, 0, 1)"> [
</span>'filter' =><span style="color: rgba(0, 0, 0, 1)"> [
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这里 key 是自定义名称</span>
'word_synonym' =><span style="color: rgba(0, 0, 0, 1)"> [
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 同义词过滤</span>
'type' => 'synonym',
'synonyms_path' => 'synonyms.txt',<span style="color: rgba(0, 0, 0, 1)">
]</span>,<span style="color: rgba(0, 0, 0, 1)">
]</span>,
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 前面说到的分析器</span>
'analyzer' =><span style="color: rgba(0, 0, 0, 1)"> [
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这里 key 是自定义名称</span>
'ik_max_word_synonym' =><span style="color: rgba(0, 0, 0, 1)"> [
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 分词器 这里用了ik分词器,其它的一些用法可以去ik github 上看下</span>
'tokenizer' => 'ik_max_word',
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 用到了上面我们自定义的过滤器</span>
'filter' => 'word_synonym',<span style="color: rgba(0, 0, 0, 1)">
]</span>,<span style="color: rgba(0, 0, 0, 1)">
]
]
];
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 对应名称
* @return string
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> getIndexName(): <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> self::<span style="color: rgba(128, 0, 128, 1)">$name</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* ES 字段MAPPING
* @return array
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> getProperties(): <span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 这里就是按照es自动生成的json改改</span>
<span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> [
</span>'id' =><span style="color: rgba(0, 0, 0, 1)"> [
</span>'type' => 'long'<span style="color: rgba(0, 0, 0, 1)">
]</span>,
'name' =><span style="color: rgba(0, 0, 0, 1)"> [
</span>'type' => 'text',
'analyzer' => 'ik_max_word',<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 存储时用上的analyzer</span>
'search_analyzer' => 'ik_max_word_synonym',<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 搜索时用上上面自定义的analyzer</span>
'fields' =><span style="color: rgba(0, 0, 0, 1)"> [
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 定义了最大长度</span>
'keyword' =><span style="color: rgba(0, 0, 0, 1)"> [
</span>'type' => 'keyword',
'ignore_above' => 256<span style="color: rgba(0, 0, 0, 1)">
]
]
]</span>,
'price' =><span style="color: rgba(0, 0, 0, 1)"> [
</span>'type' => 'float'<span style="color: rgba(0, 0, 0, 1)">
]</span>,
'category' =><span style="color: rgba(0, 0, 0, 1)"> [
</span>'type' => 'keyword'<span style="color: rgba(0, 0, 0, 1)">
]</span>,<span style="color: rgba(0, 0, 0, 1)">
];
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 分词库设置
* @return array
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> getSettings(): <span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> self::<span style="color: rgba(128, 0, 128, 1)">$settings</span><span style="color: rgba(0, 0, 0, 1)">;
}
}</span></pre>
</div>
<p><span style="font-size: 15px">好了,现在已经定义好了 Mapping 的代码结构,但是要注意的是字段的 Mapping 一旦设置好了是不能重新修改的,只能删了再重新设定。</span></p>
<p><span style="font-size: 15px">至于原因是修改字段的类型会导致 ES 索引失效,如果实在需要修改需要通过 Reindex 重建索引,这个需要使用时看下就可以了。</span></p>
<p><span style="font-size: 15px">虽然还没用上这个 Mapping 但后续只要接上就可以使用了,再整理一下代码对应的目录结构:</span></p>
<p><span style="font-size: 15px"><img src="https://img2022.cnblogs.com/blog/1340345/202203/1340345-20220304153138357-1329405568.png"></span></p>
<p><span style="font-size: 15px">index 目录中存放所有索引信息;</span></p>
<p>Config.php 用于存放配置信息;</p>
<p>ElasticsearchObj.php 目前用于获取客户端实例以及耦合了插入方法,如果操作方法太多这里可以进行功能性抽离;</p>
<p>index.php 场景类方便测试调用写的代码。</p>
<h3>基本操作</h3>
<p><span style="font-size: 15px">现在开始尝试更新索引并完善其它索引操作</span></p>
<p><span style="font-size: 15px">之前都是将客户端操作封装到 ElasticsearchObj 对象中,但索引的操作很多的话 ElasticsearchObj 就会越来越臃肿</span></p>
<p><span style="font-size: 15px">在 ElasticsearchObj 中新增一个获取客户端实例的方法方便在其它类中调用客户端实例:</span> </p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 获取ES客户端实例
*
* @return Client
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> getElasticsearchClint():<span style="color: rgba(0, 0, 0, 1)"> Client
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$this</span>-><span style="color: rgba(0, 0, 0, 1)">client;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 可以通过链式方法获取到客户端实例</span>
<span style="color: rgba(128, 0, 128, 1)">$client</span> = ElasticsearchObj::getInstance()->getElasticsearchClint();</pre>
</div>
<p><span style="font-size: 15px">上面在说 Mapping 时就已经将获取索引方法抽象为接口,这里只要面向接口编程即可。</span></p>
<p><span style="font-size: 15px">其余的操作都大同小异这里不再多说,都是拼凑出数组参数传给 ES 客户端。</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ElasticsearchIndex
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(128, 0, 128, 1)">$client</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> __construct()
{
</span><span style="color: rgba(128, 0, 128, 1)">$this</span>->client = ElasticsearchObj::getInstance()-><span style="color: rgba(0, 0, 0, 1)">getElasticsearchClint();
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 创建索引
*
* @param IndexInterface $index
* @return array
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> createIndex(IndexInterface <span style="color: rgba(128, 0, 128, 1)">$index</span>): <span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(128, 0, 128, 1)">$config</span> =<span style="color: rgba(0, 0, 0, 1)"> [
</span>'index' => <span style="color: rgba(128, 0, 128, 1)">$index</span>->getIndexName(), <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 索引名</span>
'body'=><span style="color: rgba(0, 0, 0, 1)"> [
</span>'settings' => <span style="color: rgba(128, 0, 128, 1)">$index</span>->getSettings() ?: [],
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> mappings 对应的字段属性 & 详细字段的分词规则</span>
'mappings' =><span style="color: rgba(0, 0, 0, 1)"> [
</span>'properties' => <span style="color: rgba(128, 0, 128, 1)">$index</span>->getProperties(),<span style="color: rgba(0, 0, 0, 1)">
]
]
];
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$this</span>->client->indices()->create(<span style="color: rgba(128, 0, 128, 1)">$config</span><span style="color: rgba(0, 0, 0, 1)">);
}
}</span></pre>
</div>
<p><span style="font-size: 15px">写好的代码当然要拉出来溜溜,现在如果直接执行的话会报 resource_already_exists_exception 因为上面已经创建过这个索引,这里直接去 Kibana 删除即可。</span></p>
<p><span style="font-size: 15px">在开发时碰到错误是不能避免的,但只要耐心看下错误提示的意思或者网上查下往往都能找到问题所在。</span></p>
<p><img src="https://img2022.cnblogs.com/blog/1340345/202203/1340345-20220304153825208-773070930.png"></p>
<p> </p>
<p> <img src="https://img2022.cnblogs.com/blog/1340345/202203/1340345-20220304153856775-1936930614.png"></p>
<p> </p>
<p>现在还可以完善一些对文档的增删改操作,对于文档来说相当于数据库的行。</p>
<p>更新与新增操作是可以通过 ID 确定文档的唯一性,同时在通过 PHP 操作时可以公用一个方法。</p>
<p>可以看到每次文档数据的重建,数据的版本都会增一。</p>
<p><img src="https://img2022.cnblogs.com/blog/1340345/202203/1340345-20220304154013556-1937420601.png"></p>
<p><span style="font-size: 15px">下面再新增一些删除方法即可完成增删改操作:</span></p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 删除文档
*
* @param $index
* @param $id
* @return array|callable
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> delete(<span style="color: rgba(128, 0, 128, 1)">$index</span>, <span style="color: rgba(128, 0, 128, 1)">$id</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(128, 0, 128, 1)">$params</span> =<span style="color: rgba(0, 0, 0, 1)"> [
</span>'index' => <span style="color: rgba(128, 0, 128, 1)">$index</span>,
'id' => <span style="color: rgba(128, 0, 128, 1)">$id</span><span style="color: rgba(0, 0, 0, 1)">
];
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$this</span>->client->delete(<span style="color: rgba(128, 0, 128, 1)">$params</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 通过ID列表删除文档
*
* @param $index
* @param $idList
* @return array|callable
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> deleteByIdList(<span style="color: rgba(128, 0, 128, 1)">$index</span>, <span style="color: rgba(128, 0, 128, 1)">$idList</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(128, 0, 128, 1)">$indexParams</span> =<span style="color: rgba(0, 0, 0, 1)"> [
</span>'index' => <span style="color: rgba(128, 0, 128, 1)">$index</span>,<span style="color: rgba(0, 0, 0, 1)">
];
</span><span style="color: rgba(128, 0, 128, 1)">$this</span>->client->indices()->open(<span style="color: rgba(128, 0, 128, 1)">$indexParams</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$params</span> =<span style="color: rgba(0, 0, 0, 1)"> [
</span>'body' =><span style="color: rgba(0, 0, 0, 1)"> []
];
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(128, 0, 128, 1)">$idList</span> <span style="color: rgba(0, 0, 255, 1)">as</span> <span style="color: rgba(128, 0, 128, 1)">$deleteId</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(128, 0, 128, 1)">$params</span>['body'][] =<span style="color: rgba(0, 0, 0, 1)"> [
</span>'delete' =><span style="color: rgba(0, 0, 0, 1)"> [
</span>'_index' => <span style="color: rgba(128, 0, 128, 1)">$index</span>,
'_id' => <span style="color: rgba(128, 0, 128, 1)">$deleteId</span><span style="color: rgba(0, 0, 0, 1)">
]
];
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$this</span>->client->bulk(<span style="color: rgba(128, 0, 128, 1)">$params</span><span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<h3>基本操作</h3>
<p><span style="font-size: 15px">前面的内容完成后其实已经可以自由的对es进行文档的操作了。</span></p>
<p><span style="font-size: 15px">是不是还挺简单的,后面的查询操作其实大致也是组合参数再进行查询。</span></p>
<p><span style="font-size: 15px">但ES的查询是可以嵌套的,用起来十分灵活。</span></p>
<p><span style="font-size: 15px">在写代码之前最少要知道一些必要的基础概念:</span></p>
<h4>match</h4>
<p><span style="font-size: 15px">会先将要查询的内容分词处理,分词处理后再进行搜索查询返回。</span></p>
<h4>match_all</h4>
<p><span style="font-size: 15px">查询所有,等于数据库中 where 后面没有条件。</span></p>
<h4>term</h4>
<p><span style="font-size: 15px">精准查找,不会将查询的内容分词处理,直接使用查询的内容进行搜索查询返回。</span></p>
<h4>match_phrase</h4>
<p><span style="font-size: 15px">同样会分词处理但分词的词汇必须要都匹配上才返回。</span></p>
<p><span style="font-size: 15px">详细搜索的内容可以查看 深入搜索</span></p>
<h4>查询条件组合</h4>
<h4>must</h4>
<p><span style="font-size: 15px">所有的语句都 必须(must)匹配,与 AND 等价。<br></span></p>
<h4><span style="font-size: 15px">should</span></h4>
<p><span style="font-size: 15px">至少有一个语句要匹配,与 OR 等价。</span></p>
<h4><span style="font-size: 15px">must_not</span></h4>
<p><span style="font-size: 15px">所有的语句都 不能(must not) 匹配,与 NOT 等价。<br>详细查看 组合过滤器</span></p>
<h4>在 kibana 中查询内容</h4>
<p><span style="font-size: 15px">在 kibana 上可以在 Dev Tools 中尝试使用上述内容进行查询,可以执行示例代码中的插入数据后尝试查询:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 查询ID为10的文档</span>
GET /new_index/<span style="color: rgba(0, 0, 0, 1)">_search
{
</span>"query":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"bool":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"must":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"match":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"id": 10<span style="color: rgba(0, 0, 0, 1)">
}
}
}
}
}
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 查询价格低于二十的文档</span>
GET /new_index/<span style="color: rgba(0, 0, 0, 1)">_search
{
</span>"query":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"bool":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"must":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"range":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"price":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"lt": 20<span style="color: rgba(0, 0, 0, 1)">
}
}
}
}
}
}
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 价格低于30的肉类</span>
GET /new_index/<span style="color: rgba(0, 0, 0, 1)">_search
{
</span>"query":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"bool":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"must":<span style="color: rgba(0, 0, 0, 1)"> [
{
</span>"match":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"category": "meat"<span style="color: rgba(0, 0, 0, 1)">
}
}</span>,<span style="color: rgba(0, 0, 0, 1)">
{
</span>"range":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"price":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"lt": 30<span style="color: rgba(0, 0, 0, 1)">
}
}
}
]
}
}
}
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 火腿肠或者价格低于十元</span>
GET /new_index/<span style="color: rgba(0, 0, 0, 1)">_search
{
</span>"query":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"bool":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"should":<span style="color: rgba(0, 0, 0, 1)"> [
{
</span>"match":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"name": "火腿肠"<span style="color: rgba(0, 0, 0, 1)">
}
}</span>,<span style="color: rgba(0, 0, 0, 1)">
{
</span>"range":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"price":<span style="color: rgba(0, 0, 0, 1)"> {
</span>"lt": 10<span style="color: rgba(0, 0, 0, 1)">
}
}
}
]
}
}
}</span></pre>
</div>
<h3>查询功能代码</h3>
<p><span style="font-size: 15px">通过上面内容可以发现搜索的组合是十分灵活的,如果每个业务场景的都要通过拼接数组再去用客户端查询,代码将会十分复杂(想想会有很多 if else 并且不同的场景还不一样)。</span></p>
<p><span style="font-size: 15px">所以能不能封装一层,将生成组合条件数组的部分抽离出来,通过链式调用构造查询,保证业务代码和通用代码相分离。</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 类似这样的查询</span>
<span style="color: rgba(128, 0, 128, 1)">$where</span> = ['name' => '火腿肠'<span style="color: rgba(0, 0, 0, 1)">];
</span><span style="color: rgba(128, 0, 128, 1)">$list</span> = <span style="color: rgba(128, 0, 128, 1)">$model</span>->where(<span style="color: rgba(128, 0, 128, 1)">$where</span>)->query();</pre>
</div>
<p><span style="font-size: 15px">在做这件事之前首先介绍 elastica 这个PHP包,通过包中的方法可以生成查询数组。</span></p>
<p><span style="font-size: 15px">后来写完后我翻了一下 elastica 的代码,发现 elastica 不仅可以生成条件数组而且覆盖了对 ES 操作的大部分操作,这个可以后面直接使用这个包来实现一下应该也会很棒。</span></p>
<p><span style="font-size: 15px">这里我只是用来生成数组参数来使用了,整个过程也和上述的操作很像,拼凑出一个数组参数,将数组作为参数进行传递。</span></p>
<p><span style="font-size: 15px">只要将这个数组作为类的成员变量,通过不同的方法不断的给数组中添加内容,这样就给链式调用的实现带来了可能。</span></p>
<h3>创造类</h3>
<p><span style="font-size: 15px">前面已经将不同的索引通过面向接口方式实现出来了,再通过构造注入方式将实例注入到类中。</span></p>
<p><span style="font-size: 15px">下面的代码通过链式调用实现了一些类似分页这样基础的功能。</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ElasticModelService
{
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(128, 0, 128, 1)">$client</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(128, 0, 128, 1)">$index</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(128, 0, 128, 1)">$condition</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(128, 0, 128, 1)">$search</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(128, 0, 128, 1)">$fields</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> __construct(IndexInterface <span style="color: rgba(128, 0, 128, 1)">$index</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(128, 0, 128, 1)">$this</span>->client = ElasticsearchObj::getInstance()-><span style="color: rgba(0, 0, 0, 1)">getElasticsearchClint();
</span><span style="color: rgba(128, 0, 128, 1)">$this</span>->setIndex(<span style="color: rgba(128, 0, 128, 1)">$index</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$this</span>-><span style="color: rgba(0, 0, 0, 1)">initModel();
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$this</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 初始化索引模型
*
* @throws \Exception
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> initModel()
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 重置条件</span>
<span style="color: rgba(128, 0, 128, 1)">$this</span>-><span style="color: rgba(0, 128, 128, 1)">reset</span><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 索引名</span>
<span style="color: rgba(128, 0, 128, 1)">$this</span>->search['index'] = <span style="color: rgba(128, 0, 128, 1)">$this</span>->index-><span style="color: rgba(0, 0, 0, 1)">getAliasName();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> fields</span>
<span style="color: rgba(128, 0, 128, 1)">$mapping</span> = <span style="color: rgba(128, 0, 128, 1)">$this</span>->index-><span style="color: rgba(0, 0, 0, 1)">getProperties();
</span><span style="color: rgba(128, 0, 128, 1)">$this</span>->fields = <span style="color: rgba(0, 128, 128, 1)">array_keys</span>(<span style="color: rgba(128, 0, 128, 1)">$mapping</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 重置查询
*
* @return $this
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> <span style="color: rgba(0, 128, 128, 1)">reset</span>():<span style="color: rgba(0, 0, 0, 1)"> ElasticModelService
{
</span><span style="color: rgba(128, 0, 128, 1)">$this</span>->condition =<span style="color: rgba(0, 0, 0, 1)"> [];
</span><span style="color: rgba(128, 0, 128, 1)">$this</span>->search =<span style="color: rgba(0, 0, 0, 1)"> [];
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$this</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 设置过滤参数
*
* @param array $fields
* @return $this
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> fields(<span style="color: rgba(0, 0, 255, 1)">array</span> <span style="color: rgba(128, 0, 128, 1)">$fields</span>):<span style="color: rgba(0, 0, 0, 1)"> ElasticModelService
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 255, 1)">empty</span>(<span style="color: rgba(128, 0, 128, 1)">$fields</span><span style="color: rgba(0, 0, 0, 1)">)) {
</span><span style="color: rgba(128, 0, 128, 1)">$this</span>->search['body']['_source'] = <span style="color: rgba(128, 0, 128, 1)">$fields</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$this</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 分页查询参数
*
* @param int $page
* @param int $pageSize
* @return $this
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> pagination(int <span style="color: rgba(128, 0, 128, 1)">$page</span>, int <span style="color: rgba(128, 0, 128, 1)">$pageSize</span>):<span style="color: rgba(0, 0, 0, 1)"> ElasticModelService
{
</span><span style="color: rgba(128, 0, 128, 1)">$this</span>->search['size'] = <span style="color: rgba(128, 0, 128, 1)">$pageSize</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$fromNum</span> = (<span style="color: rgba(128, 0, 128, 1)">$page</span> - 1) * <span style="color: rgba(128, 0, 128, 1)">$pageSize</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 0, 128, 1)">$this</span>->setFrom((int)<span style="color: rgba(128, 0, 128, 1)">$fromNum</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$this</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 设置开始查询位置
*
* @param int $from
* @return $this
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> setFrom(int <span style="color: rgba(128, 0, 128, 1)">$from</span>):<span style="color: rgba(0, 0, 0, 1)"> ElasticModelService
{
</span><span style="color: rgba(128, 0, 128, 1)">$this</span>->search['from'] = <span style="color: rgba(128, 0, 128, 1)">$from</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$this</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 设置查询大小
*
* @param int $size
* @return $this
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> setSize(int <span style="color: rgba(128, 0, 128, 1)">$size</span>):<span style="color: rgba(0, 0, 0, 1)"> ElasticModelService
{
</span><span style="color: rgba(128, 0, 128, 1)">$this</span>->search['size'] = <span style="color: rgba(128, 0, 128, 1)">$size</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$this</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 设置索引名
*
* @param IndexInterface $index
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">function</span> setIndex(IndexInterface <span style="color: rgba(128, 0, 128, 1)">$index</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(128, 0, 128, 1)">$this</span>->index = <span style="color: rgba(128, 0, 128, 1)">$index</span><span style="color: rgba(0, 0, 0, 1)">;
}
}</span></pre>
</div>
<p><span style="font-size: 15px">在上面的基础上可以尝试写一些简单的查询构造方法在类中,如下面代码片段:</span></p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 传入 ['name' => '火腿肠'],返回对象方便后面再次用链式调用</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> where(<span style="color: rgba(0, 0, 255, 1)">array</span> <span style="color: rgba(128, 0, 128, 1)">$where</span>):<span style="color: rgba(0, 0, 0, 1)"> ElasticModelService
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 遍历条件数组</span>
<span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(128, 0, 128, 1)">$where</span> <span style="color: rgba(0, 0, 255, 1)">as</span> <span style="color: rgba(128, 0, 128, 1)">$field</span> => <span style="color: rgba(128, 0, 128, 1)">$value</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 利用 elastica 包生成查询数组 </span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 128, 128, 1)">is_numeric</span>(<span style="color: rgba(128, 0, 128, 1)">$value</span><span style="color: rgba(0, 0, 0, 1)">)) {
</span><span style="color: rgba(128, 0, 128, 1)">$query</span> = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Term();
</span><span style="color: rgba(128, 0, 128, 1)">$query</span>->setTerm(<span style="color: rgba(128, 0, 128, 1)">$field</span>, <span style="color: rgba(128, 0, 128, 1)">$value</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$match</span> = <span style="color: rgba(128, 0, 128, 1)">$query</span>-><span style="color: rgba(0, 0, 0, 1)">toArray();
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(128, 0, 128, 1)">$matchQuery</span> = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MatchPhrase();
</span><span style="color: rgba(128, 0, 128, 1)">$match</span> = <span style="color: rgba(128, 0, 128, 1)">$matchQuery</span>->setFieldQuery(<span style="color: rgba(128, 0, 128, 1)">$field</span>, <span style="color: rgba(128, 0, 128, 1)">$value</span>)-><span style="color: rgba(0, 0, 0, 1)">toArray();
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(128, 0, 128, 1)">$match</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 更改类中成员变量的数据</span>
<span style="color: rgba(128, 0, 128, 1)">$this</span>->condition['must'][] = <span style="color: rgba(128, 0, 128, 1)">$match</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$this</span><span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<p><span style="font-size: 15px">这样实现了简单版的 where 构造方法只要认真看下代码应该不难理解,但后面再加上一些其它操作方法的代码量会累积的很多。</span></p>
<p><span style="font-size: 15px">准备进一步拆分,将能够复用的部分代码拆成一部分,根据不同的需要调用这些方法。</span></p>
<p><span style="font-size: 15px">并且在 where 方法中加上一些兼容处理。</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> where(<span style="color: rgba(0, 0, 255, 1)">array</span> <span style="color: rgba(128, 0, 128, 1)">$where</span>):<span style="color: rgba(0, 0, 0, 1)"> ElasticModelService
{
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(128, 0, 128, 1)">$where</span> <span style="color: rgba(0, 0, 255, 1)">as</span> <span style="color: rgba(128, 0, 128, 1)">$field</span> => <span style="color: rgba(128, 0, 128, 1)">$value</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(128, 0, 128, 1)">$realField</span> = <span style="color: rgba(128, 0, 128, 1)">$this</span>->getRealField(<span style="color: rgba(128, 0, 128, 1)">$field</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 128, 128, 1)">in_array</span>(<span style="color: rgba(128, 0, 128, 1)">$realField</span>, <span style="color: rgba(128, 0, 128, 1)">$this</span>-><span style="color: rgba(0, 0, 0, 1)">fields)) {
</span><span style="color: rgba(128, 0, 128, 1)">$match</span> = <span style="color: rgba(128, 0, 128, 1)">$this</span>->getFilterMatch(<span style="color: rgba(128, 0, 128, 1)">$field</span>, <span style="color: rgba(128, 0, 128, 1)">$value</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(128, 0, 128, 1)">$match</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(128, 0, 128, 1)">$this</span>->condition['must'][] = <span style="color: rgba(128, 0, 128, 1)">$match</span><span style="color: rgba(0, 0, 0, 1)">;
}
}
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$this</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 加上一些增加功能如可以传 ['id|in' => ] 或者 ['date|gt' => '2022-01-01'] </span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> getRealField(<span style="color: rgba(0, 0, 255, 1)">string</span> <span style="color: rgba(128, 0, 128, 1)">$field</span>): <span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(128, 0, 128, 1)">$tempField</span> = <span style="color: rgba(128, 0, 128, 1)">$field</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 128, 128, 1)">strpos</span>(<span style="color: rgba(128, 0, 128, 1)">$field</span>, '|') !== <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(128, 0, 128, 1)">$fields</span> = <span style="color: rgba(0, 128, 128, 1)">explode</span>('|', <span style="color: rgba(128, 0, 128, 1)">$field</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$tempField</span> = (<span style="color: rgba(0, 0, 255, 1)">string</span>)<span style="color: rgba(128, 0, 128, 1)">$fields</span>;
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$tempField</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">function</span> getFilterMatch(<span style="color: rgba(128, 0, 128, 1)">$field</span>, <span style="color: rgba(128, 0, 128, 1)">$value</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 128, 128, 1)">strpos</span>(<span style="color: rgba(128, 0, 128, 1)">$field</span>, '|') !== <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 范围搜索</span>
<span style="color: rgba(128, 0, 128, 1)">$rangeField</span> = <span style="color: rgba(0, 128, 128, 1)">explode</span>('|', <span style="color: rgba(128, 0, 128, 1)">$field</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 128, 128, 1)">count</span>(<span style="color: rgba(128, 0, 128, 1)">$rangeField</span>) != 2<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">switch</span> (<span style="color: rgba(0, 128, 128, 1)">strtolower</span>(<span style="color: rgba(128, 0, 128, 1)">$rangeField</span>)) {
</span><span style="color: rgba(0, 0, 255, 1)">case</span> 'in':
<span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$this</span>->_getMatch(<span style="color: rgba(128, 0, 128, 1)">$rangeField</span>, <span style="color: rgba(128, 0, 128, 1)">$value</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">case</span> 'notin':
<span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$this</span>->_getMatch(<span style="color: rgba(128, 0, 128, 1)">$rangeField</span>, <span style="color: rgba(128, 0, 128, 1)">$value</span>,'must_not'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">default</span>:
<span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$this</span>->_getRangeMatch(<span style="color: rgba(128, 0, 128, 1)">$rangeField</span>, <span style="color: rgba(128, 0, 128, 1)">$rangeField</span>, <span style="color: rgba(128, 0, 128, 1)">$value</span><span style="color: rgba(0, 0, 0, 1)">);
}
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 等值查询</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$this</span>->_getMatch(<span style="color: rgba(128, 0, 128, 1)">$field</span>, <span style="color: rgba(128, 0, 128, 1)">$value</span><span style="color: rgba(0, 0, 0, 1)">);
}
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">function</span> _getMatch(<span style="color: rgba(128, 0, 128, 1)">$field</span>, <span style="color: rgba(128, 0, 128, 1)">$value</span>, <span style="color: rgba(0, 0, 255, 1)">string</span> <span style="color: rgba(128, 0, 128, 1)">$operate</span> = 'should'): <span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(128, 0, 128, 1)">$match</span> =<span style="color: rgba(0, 0, 0, 1)"> [];
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 128, 128, 1)">is_array</span>(<span style="color: rgba(128, 0, 128, 1)">$value</span><span style="color: rgba(0, 0, 0, 1)">)) {
</span><span style="color: rgba(128, 0, 128, 1)">$matchQuery</span> = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MatchQuery();
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> (<span style="color: rgba(128, 0, 128, 1)">$value</span> <span style="color: rgba(0, 0, 255, 1)">as</span> <span style="color: rgba(128, 0, 128, 1)">$valId</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(128, 0, 128, 1)">$match</span>['bool'][<span style="color: rgba(128, 0, 128, 1)">$operate</span>][] = <span style="color: rgba(128, 0, 128, 1)">$matchQuery</span>->setFieldQuery(<span style="color: rgba(128, 0, 128, 1)">$field</span>, <span style="color: rgba(128, 0, 128, 1)">$valId</span>)-><span style="color: rgba(0, 0, 0, 1)">toArray();
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(128, 0, 128, 1)">$operate</span> == 'should'<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(128, 0, 128, 1)">$match</span>['bool']['minimum_should_match'] = 1<span style="color: rgba(0, 0, 0, 1)">;
}
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 128, 128, 1)">is_numeric</span>(<span style="color: rgba(128, 0, 128, 1)">$value</span><span style="color: rgba(0, 0, 0, 1)">)) {
</span><span style="color: rgba(128, 0, 128, 1)">$query</span> = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Term();
</span><span style="color: rgba(128, 0, 128, 1)">$query</span>->setTerm(<span style="color: rgba(128, 0, 128, 1)">$field</span>, <span style="color: rgba(128, 0, 128, 1)">$value</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$match</span> = <span style="color: rgba(128, 0, 128, 1)">$query</span>-><span style="color: rgba(0, 0, 0, 1)">toArray();
} </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(128, 0, 128, 1)">$matchQuery</span> = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MatchPhrase();
</span><span style="color: rgba(128, 0, 128, 1)">$match</span> = <span style="color: rgba(128, 0, 128, 1)">$matchQuery</span>->setFieldQuery(<span style="color: rgba(128, 0, 128, 1)">$field</span>, <span style="color: rgba(128, 0, 128, 1)">$value</span>)-><span style="color: rgba(0, 0, 0, 1)">toArray();
}
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$match</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">function</span> _getRangeMatch(<span style="color: rgba(128, 0, 128, 1)">$field</span>, <span style="color: rgba(128, 0, 128, 1)">$operator</span>, <span style="color: rgba(128, 0, 128, 1)">$value</span>): <span style="color: rgba(0, 0, 255, 1)">array</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(128, 0, 128, 1)">$range</span> = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 128, 128, 1)">Range</span><span style="color: rgba(0, 0, 0, 1)">();
</span><span style="color: rgba(128, 0, 128, 1)">$range</span>->addField(<span style="color: rgba(128, 0, 128, 1)">$field</span>, [<span style="color: rgba(128, 0, 128, 1)">$operator</span> => <span style="color: rgba(128, 0, 128, 1)">$value</span><span style="color: rgba(0, 0, 0, 1)">]);
</span><span style="color: rgba(128, 0, 128, 1)">$match</span> =<span style="color: rgba(0, 0, 0, 1)"> [];
</span><span style="color: rgba(128, 0, 128, 1)">$match</span>['bool']['must'] = <span style="color: rgba(128, 0, 128, 1)">$range</span>-><span style="color: rgba(0, 0, 0, 1)">toArray();
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 128, 1)">$match</span><span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<p><span style="font-size: 15px">拆分后代码虽然看起来变更多了,但代码的功能和复用性也增强了。</span></p>
<p><span style="font-size: 15px">很容易发现一些基础的方法可以使用 Trait 集中起来以此提高可读性。</span></p>
<p><span style="font-size: 15px">其它的功能这里也不再赘述可以看下整体代码。</span></p>
<h3>测试调用</h3>
<p><span style="font-size: 15px">虽然看起来还有很多可以优化的地方,但至少一个简易的 ES 操作代码就完成了。</span></p>
<p><span style="font-size: 15px">先跑起来测试一下。</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(128, 0, 128, 1)">$itemsIndex</span> = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ItemsIndex();
</span><span style="color: rgba(128, 0, 128, 1)">$itemModel</span> = <span style="color: rgba(0, 0, 255, 1)">new</span> ElasticModelService(<span style="color: rgba(128, 0, 128, 1)">$itemsIndex</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(128, 0, 128, 1)">$queryList</span> = <span style="color: rgba(128, 0, 128, 1)">$itemModel</span>->where(['id' => 11])->fields(['name', 'id', 'price'])-><span style="color: rgba(0, 0, 0, 1)">query();
</span><span style="color: rgba(0, 128, 128, 1)">var_dump</span>(<span style="color: rgba(128, 0, 128, 1)">$queryList</span>);</pre>
</div>
<p><img src="https://img2022.cnblogs.com/blog/1340345/202203/1340345-20220304170157188-1675148325.png"></p>
<h3>文档之间的关联</h3>
<p>在实际使用时可能还会出现类似数据库连表的场景,但这并不是 ES 的强项。</p>
<p>这时需要了解嵌套类型 nested 或者 父子文档组合。</p>
<p>nested 是文档中嵌套文档,而父子文档通过 index 之间进行关联。</p>
<p>因为父子文档的性能问题,建议非要使用的话就使用 nested。</p>
<p>详情可以查看文档。</p>
<p>并且 ES 对于 nested 查询是有单独的语法,这个还需要单独处理。</p><br><br>
来源:https://www.cnblogs.com/caiawo/p/15931090.html
頁:
[1]