Laravel 中优雅的使用 Elasticsearch

最近公司开始使用ES,发现没有特别好用的扩展。
Laravel官方提供了一个Scout扩展,但这个扩展太局限,文档也含糊的要命,所以决定自己写一个扩展来适配Laravel。
首先发现了一个比较好用DSL,ongr-io/ElasticsearchDSL,这个扩展几乎涵盖了所有的ES操作。
那怎么才能在Laravel中像ORM一样使用呐,那就参考Scout扩展了(这个低配版我真不想用),
源码我已经放在了GitHub上,这次的版本和之前做了结构上的调整,更加好用,而且已经在项目中使用,我会一直维护这个扩展包。
这次的重构,主要是借鉴了ORM中Builder的实现,放弃了Scout拗口的特性。

直接贴使用代码

使用说明

生成Mapping

通过读取注解实现

<?php
<?php
namespace App\Entities;


use Golly\Elastic\Hydrate\Entity;
use Golly\Elastic\Hydrate\Annotations\Mapping;
use Golly\Elastic\Exceptions\ElasticException;

/**
 * Class UserEntity
 * @package App\Entities
 */
class UserEntity extends Entity
{

    /**
     * @Mapping(type="long")
     *
     * @var int
     */
    public $id;

    /**
     * @Mapping(type="text", analyzer="ik_smart")
     *
     * @var
     */
    public $name;

    /**
     * @Mapping(type="date", format="Y-m-d")
     *
     * @var string
     */
    public $date;

    /**
     * @Mapping(type="relation")
     *
     * @var array|UserAddressEntity[]
     */
    public $address = [];

    /**
     * @param boolean $relation
     * @return array
     * @throws ElasticException
     */
    public static function mapping($relation = true)
    {
        $mapping = parent::mapping($relation);
        if ($relation) {
            $mapping = array_merge($mapping, [
                'address' => [
                    'properties' => UserAddressEntity::mapping($relation)
                ]
            ]);
        }

        return $mapping;
    }

    /**
     * @param array $data
     * @param boolean $original
     * @return UserEntity
     * @throws ElasticException
     */
    public function toObject(array $data, $original = true)
    {
        $entity = parent::toObject($data, $original);
        if ($entity->address) {
            foreach ($entity->address as &$address) {
                $address = UserAddressEntity::instance($address, $original);
            }
        }

        return $entity;
    }
}

获取

<?php
use App\Entities\UserEntity;

dd(UserEntity::mapping());

打印结构

array:4 [
  "id" => array:1 [
    "type" => "long"
  ]
  "name" => array:2 [
    "type" => "text"
    "analyzer" => "ik_smart"
  ]
  "date" => array:2 [
    "type" => "date"
    "format" => "yyyy-MM-dd"
  ]
  "address" => array:1 [
    "properties" => array:1 [
      "city" => array:1 [
        "type" => "keyword"
      ]
    ]
  ]
]

如何检索

在orm中,可直接用DB门面类来执行MySQL操作,相当于实例化Illuminate\Database\Query\Builder类。

那么ES应该也可以这样操作,所以我也加入了一个ElasticBuilder

当然Model模型依赖可以按原来的方式操作,最终都是ElasticBuilder来构造查询参数

(new ElasticBuilder())
    ->select(['id'])
    ->from('index')
    ->where(function (ElasticBuilder $query) {
        $query->where('name', '张三');
    })->should(function (ElasticBuilder $query) {
        $query->where(
            'id', 1
        )->where(
            'id', 2
        );
    })->whereNotIn(
        'name', ['李四']
    )->dd();

User::elastic()
    ->where(function (Builder $query) {
        $query->where('name', '张三');
    })->should(function (Builder $query) {
        $query->where(
            'id', 1
        )->where(
            'id', 2
        );
    })->whereNotIn(
        'name', ['李四']
    )->dd();

最终打印的结果是(这里我仅贴了一个,唯一 的区别就是’index’不同)

array:3 [
  "index" => "index"
  "_source" => array:1 [
    0 => "id"
  ]
  "body" => array:1 [
    "query" => array:1 [
      "bool" => array:3 [
        "must" => array:1 [
          0 => array:1 [
            "bool" => array:1 [
              "must" => array:1 [
                0 => array:1 [
                  "term" => array:1 [
                    "name" => "张三"
                  ]
                ]
              ]
            ]
          ]
        ]
        "should" => array:1 [
          0 => array:1 [
            "bool" => array:1 [
              "must" => array:2 [
                0 => array:1 [
                  "term" => array:1 [
                    "id" => "1"
                  ]
                ]
                1 => array:1 [
                  "term" => array:1 [
                    "id" => "2"
                  ]
                ]
              ]
            ]
          ]
        ]
        "must_not" => array:1 [
          0 => array:1 [
            "terms" => array:1 [
              "name" => array:1 [
                0 => "李四"
              ]
            ]
          ]
        ]
      ]
    ]
  ]
]

直接通过ES检索数据

User::elastic()->getRaw()

通过ES检索数据后,执行MySQL查询

User::elastic()->get();

该操作支持with,withCount

直接通过ES检索数据后分页

User::elastic()->paginateRaw();

通过ES检索后,执行MySQL查询并分页

User::elastic()->paginate();
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 14

看上去不错 给lz 加油。

3年前 评论
kabunx (楼主) 3年前

👍 支持下,下面这些分享给你,虽然我都没用过!😂

Community DSLs 社区提供的 DSL,第一个就是 ongr-io/ElasticsearchDSL

Community Integrations 社区整合包,里面有两个 Laravel 的

3年前 评论
kabunx (楼主) 3年前
lddtime (作者) 3年前

很赞 小心心送给你

3年前 评论

在实际开发中,还是发现了一些问题,比如创建和更新时无法处理关联关系,查询时orm的一些特性无法优雅的结合起来,所以近期我会仔细考虑下如何处理这些问题,由于分词(ik)需要维护词库,所以like查询将使用wildcard查询。目前使用下来,在涉及到模糊查询的情况下,走es基本带来5-8倍的性能提升

3年前 评论
sparkinzy 3年前
kabunx (作者) (楼主) 3年前
ab0029 3年前

在orm中,当使用where(’name‘,'张三')->orWhere(’name‘, '李四'),最终sql是where name='张三' or name='李四',在ES中需要放在boolshould下实现,个人能力有限,没有很好的拼接实现,所以我准备更多的使用es中的dsl作为方法名称

3年前 评论
sparkinzy 3年前

:speak_no_evil:牛啊!楼主还会继续维护这个组件吗?

2年前 评论
kabunx (楼主) 2年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!