dmstr / yii2-active-record-search
高度可定制的ActiveRecord搜索索引器
1.0.6
2024-05-08 07:17 UTC
Requires
- 2amigos/yii2-translateable-behavior: ^1.1.0
- yiisoft/yii2: ~2.0.0
- yiisoft/yii2-bootstrap: ~2.0.0
This package is auto-updated.
Last update: 2024-09-08 08:15:30 UTC
README
此模块提供了一种简单但非常灵活的方式来创建几乎任何ActiveRecord条目的搜索索引。
基本思路是
- 我们希望为每个(配置的)应用语言创建一个搜索索引
- 我们希望将不同类型的模型分组在搜索结果中
- 我们希望能够为不同类型的模型获取简单的字符串,在这些字符串上我们可以执行简单的LIKE SQL查询。无论这些字符串的数据来自哪里。
- 我们希望搜索模块的前端快速,因此应该预先定义所有构建结果链接所需的信息
模块配置
请参阅模块以获取可用的模块参数。这些参数应该允许自定义模块的几乎所有方面及其行为。
简单示例配置
'modules' => [ 'search' => [ 'class' => \dmstr\activeRecordSearch\Module::class, 'layout' => '@backend/views/layouts/box', 'frontendLayout' => '@app/views/layouts/container', ], ]
模块提供了两种类型的控制器
- 前端
\dmstr\activeRecordSearch\controllers\FrontendController
- 后端
\dmstr\activeRecordSearch\controllers\SearchGroupController
,用于管理(翻译、启用/禁用)由索引器创建的搜索组\dmstr\activeRecordSearch\controllers\SearchController
,用于管理搜索项,注意这些将在下一次索引器运行时被覆盖。通常不需要\dmstr\activeRecordSearch\controllers\SearchGroupTranslationController
,搜索组翻译。通常不需要
此外,模块还提供了一个简单的搜索输入小部件
\dmstr\activeRecordSearch\widgets\SearchInput
,如果未通过模块的searchInputWidget
属性覆盖,则将在前端控制器中使用
索引器
此模块的核心是SearchIndexer。
索引器必须为应被索引的所有类型的模型进行配置。
请参阅SearchIndexer以获取可用的模块参数和示例。
运行索引器
应将索引器定义为并作为yii cli命令调用
$config['controllerMap']['search-index'] = \dmstr\activeRecordSearch\commands\IndexController::class;
要自动化索引器运行,请创建cron作业。
可以作为cron运行的示例脚本
#!/bin/bash
. /root/export-env
LOG=/tmp/search-index-update
date > $LOG
yii search-index/update >> $LOG
date >> $LOG
简单的索引器配置示例
- 在此示例中,我们索引了两种类型的模型(产品和配件)
- 对于这两种类型,我们定义了AR模型类。这些将通过调用它们的
find()->all()
方法来获取“数据” - 我们将用于搜索的字符串将通过定义的
attributes
的值构建(连接) - 对于这两种类型,我们定义了用于构建结果URL的路线
- 我们还定义了用于构建结果URL的url_params
link_text
定义了结果链接的文本
$config['components']['searchIndexer'] = [ 'class' => \dmstr\activeRecordSearch\components\SearchIndexer::class, 'languages' => function() { return project\components\CountryHelper::activeLanguages(); }, 'fallbackLanguage' => 'en', 'searchItems' => [ 'products' => [ 'model_class' => Product::class, 'route' => '/frontend/product/detail', 'attributes' => [ 'name', 'desc' ], 'url_params' => ['productId' => 'id'], 'link_text' => 'name', 'group' => 'Products', ], 'accessories' => [ 'model_class' => Accessory::class, 'route' => '/frontend/accessory/detail', 'attributes' => [ 'name', 'desc' ], 'url_params' => ['accessoryId' => 'id'], 'link_text' => 'name', 'group' => 'P&A', ], ], ];
复杂的索引器配置示例
- 在此处,我们定义了一组不同的类型,您可以看到几乎每个参数都可以是一个回调,因此可以定义“非静态”的结果。
find_method
参数可用于覆盖默认的find()。例如,可用于通过其状态标志过滤模型- 可以在几乎任何地方使用回调,例如,从多个属性或甚至从不同模型的属性中生成link_text值(请参阅我们在标签中使用带前缀的name的tagGroup关系模型)
products['attributes']
是一个示例,其中您可以看到如何使用简单的数组表示法定义来自关系模型的虚拟属性news['attributes']['content']
是一个如何获取json结构中'content'部分的示例- 如果您有SEO URL规则,可以定义所有所需的
url_params
- ...
$config['components']['searchIndexer'] = [ 'class' => \dmstr\activeRecordSearch\components\SearchIndexer::class, 'languages' => function() { return project\components\CountryHelper::activeLanguages(); }, 'searchItems' => [ 'products' => [ 'model_class' => Product::class, 'find_method' => function ($item) { return $item['model_class']::find()->andWhere(['archived' => 0]); }, 'route' => '/frontend/product/detail', 'attributes' => [ 'name', 'frame', 'tags' => ['name'], ], 'url_params' => ['productId' => 'id', 'productName' => 'name'], 'link_text' => function ($item) { $parts = []; if ($item->getClassificationTag()) { $cTag = $item->getClassificationTag(); $cTag !== null && $parts[] = $cTag->name; } $parts[] = $item->name; return implode(': ', array_filter($parts)); }, 'group' => 'Products', ], 'product-archive' => [ 'model_class' => Product::class, 'find_method' => function ($item) { return $item['model_class']::find()->andWhere(['archived' => 1]); }, 'route' => '/frontend/product/detail', 'attributes' => [ 'name', 'frame', 'tags' => ['name'], ], 'url_params' => ['productId' => 'id', 'productName' => 'name'], 'link_text' => function ($item) { $parts = []; if ($item->getClassificationTag()) { $cTag = $item->getClassificationTag(); $cTag !== null && $parts[] = $cTag->name; } $parts[] = $item->name; return implode(': ', array_filter($parts)); }, 'group' => 'Products Archive', ], 'accessories' => [ 'model_class' => Accessory::class, 'route' => '/frontend/accessory/detail', 'attributes' => [ 'name', 'tags' => ['name'], ], 'url_params' => ['accessoryId' => 'id'], 'link_text' => 'productName', 'group' => 'P&A', ], 'news' => [ 'model_class' => PublicationItem::class, 'route' => '/publication/default/detail', 'attributes' => [ 'title', 'content' => function ($item) { $content = Json::decode($item->content_widget_json); return html_entity_decode(strip_tags($content['text_html'])); } ], 'url_params' => [ 'itemId' => 'id', 'title' => function ($item) { return $item->title; } ], 'link_text' => 'title', 'group' => 'News', ], 'tags' => [ 'model_class' => \project\modules\cruds\models\Tag::class, 'route' => '/productfinder/default/index', 'find_method' => function ($item) { // get used tagIds from finder $tag_ids = \project\modules\productfinder\models\Productfinder::getFacetIdList('tag_ids'); return $item['model_class']::find()->andWhere(['id' => $tag_ids]); }, 'attributes' => [ 'name', ], 'url_params' => [ 'mainTag' => 'id', 'mainTagName' => 'name', ], 'link_text' => function ($item) { return implode(': ', [$item->tagGroup->name, $item->name]); }, 'group' => 'Product Tags', ], ], ];