Navigate (top): Top · Features · Framework support · Requirements · Installation · Framework-specific setup · Configuration · Model setup · Basic usage · Advanced builder · Artisan / Webman commands · Queues · Builder reference · References · License
Driver-based full-text search for Eloquent models, inspired by Laravel Scout and shopwwi/webman-scout. This fork adds time ranges, aggregations, OpenSearch, vector / geo helpers, and clearer multi-framework configuration.
Languages: this file is English (default). 简体中文
- Scout-like API for easy migration from Laravel Scout / shopwwi webman-scout
- Engines: OpenSearch, Elasticsearch, Meilisearch, Typesense, Algolia, XunSearch, Database, Collection, Null
- OpenSearch-first advanced queries: aggregations, facets, KNN, geo distance
- Optional queue-driven indexing (Webman Redis Queue when available)
- Index settings sync, soft deletes, chunked import
Runtime integration targets applications that expose Laravel’s config() helper and an Illuminate container (app()), with Eloquent (Illuminate\Database\Eloquent\Model) models.
| Framework | Versions | Notes |
|---|---|---|
| Webman | 1.x / 2.x | Default install: plugin config under config/plugin/erikwang2013/webman-scout/. |
| Laravel | 7.x – 11.x | Copy the plugin app.php array into config/scout.php (or config/erikwang2013.webman-scout.php) and set SCOUT_CONFIG_KEY (see below). Requires PHP 8.0+ (Laravel 7 on PHP 8 is supported in recent 7.x releases). |
| Hyperf | 2.x – 3.x | Use Hyperf’s config + DI; Hyperf\Database\Model is Eloquent-compatible. Map Scout options into config and set SCOUT_CONFIG_KEY if not using the Webman plugin path. |
| ThinkPHP | 6.x / 8.x | Use when the app loads Illuminate config / app() (e.g. hybrid setups or illuminate/database Eloquent models). Native think\Model is not wired to the Searchable trait; call engine APIs manually or use Eloquent models for indexed entities. |
Composer requires illuminate/* ^7.0 – ^11.0 and symfony/console ^5.4 – ^7.0 so dependency resolution matches your framework stack.
- PHP ^8.0
- Eloquent models for the
Searchabletrait illuminate/bus,contracts,database,http,pagination,queue,support(versions aligned with your Laravel / Hyperf / ThinkPHP stack)
composer require erikwang2013/webman-scoutThe Composer autoload files entry loads helpers.php, which defines app(), event(), and scout_config() when needed and registers EngineManager.
After install, run the plugin installer (copies config and queue consumers):
- Config:
config/plugin/erikwang2013/webman-scout/ - Consumers:
app/queue/redis/search/
- Copy the contents of
src/config/plugin/erikwang2013/webman-scout/app.phpinto your application config, e.g.config/scout.php, returning the same associative array (keys:driver,prefix,opensearch,meilisearch, …). - Set environment variable
SCOUT_CONFIG_KEY=scout(no trailing dot) so lookups useconfig('scout.driver'), etc., instead of the Webman plugin path. - Ensure your bootstrap registers Illuminate’s
configrepository and container soconfig()andapp()resolveEngineManagerand engine clients.
If SCOUT_CONFIG_KEY is unset, the package prefers config('plugin.erikwang2013.webman-scout.app') when that array exists; otherwise it tries scout or erikwang2013.webman-scout.
The following sections assume composer require erikwang2013/webman-scout is already done.
- Enable the plugin in your Webman project (per Webman plugins) so that
config/plugin/erikwang2013/webman-scout/is published. If your stack runs the packageInstallstep, it copies plugin config and queue consumers; otherwise copy fromvendor/erikwang2013/webman-scout/src/config/plugin/erikwang2013/webman-scout/into your project. - Config lives at
config/plugin/erikwang2013/webman-scout/app.php. You normally do not setSCOUT_CONFIG_KEYsoscout_config()resolves this path automatically. - Console: commands are registered via
config/plugin/erikwang2013/webman-scout/command.php(e.g.php webman scout:import "App\\Model\\Product"— adjust namespace to your app). - Models usually extend
support\Model(Eloquent-based) anduse Searchable. - Queues (optional): install/configure webman/redis-queue, set
'queue' => truein Scout config, and run consumers underapp/queue/redis/search/(scout_make,scout_remove). If Redis Queue is missing orqueueis false, indexing runs synchronously in the request/process.
-
Config file: add
config/scout.phpthatreturns the same structure as this package’ssrc/config/plugin/erikwang2013/webman-scout/app.php(keys:driver,prefix,opensearch,meilisearch,queue, …).- Avoid loading both
laravel/scoutand this package under the sameconfig/scout.phpunless you know how to separate them; this package is a standalone Scout-style implementation.
- Avoid loading both
-
Environment: in
.envsetSCOUT_CONFIG_KEY=scoutsoscout_config('driver')readsconfig('scout.driver'). -
Container:
helpers.phpregistersEngineManager(and Meilisearch client when installed) on the activeapp()container. If you bootstrap before Composer’sfilesautoload, register the same bindings inAppServiceProvider::register():$this->app->singleton(\Erikwang2013\WebmanScout\EngineManager::class, function ($app) { return new \Erikwang2013\WebmanScout\EngineManager($app); });
-
Artisan commands: commands are Symfony
Commandclasses with names likescout:import. Register them with the framework:-
Laravel 10 and below — in
app/Console/Kernel.php:protected $commands = [ \Erikwang2013\WebmanScout\Command\ImportCommand::class, \Erikwang2013\WebmanScout\Command\FlushCommand::class, \Erikwang2013\WebmanScout\Command\IndexCommand::class, \Erikwang2013\WebmanScout\Command\DeleteIndexCommand::class, \Erikwang2013\WebmanScout\Command\DeleteAllIndexesCommand::class, \Erikwang2013\WebmanScout\Command\QueueImportCommand::class, \Erikwang2013\WebmanScout\Command\SyncIndexSettingsCommand::class, ];
-
Laravel 11+ — in
bootstrap/app.phpuse->withCommands([...])with the same class list (see Laravel 11 structure).
-
-
Models extend
Illuminate\Database\Eloquent\Model(or your base model) anduse Searchable. -
Queues: async indexing in this package is wired to Webman\RedisQueue when that class exists. On stock Laravel, keep
'queue' => falsein Scout config so changes are applied synchronously, or implement your own pipeline (e.g. dispatch a Laravel job from model observers) usingsyncMakeSearchable/ engineupdate()as a reference.
- Config: place the Scout array under a Hyperf config file, e.g.
config/autoload/scout.php, returning the same keys as the packageapp.php. SetSCOUT_CONFIG_KEY=scoutin the environment Hyperf reads (soconfig('scout')is the root array). config()/app(): Hyperf providesconfig(); ensure the IlluminateContaineris the one returned byapp()if you rely on packagehelpers.php, or bindEngineManagerin a HyperfConfigProvider/ dependency injection config pointing at your container bridge.- Models:
Hyperf\Database\Modelis Eloquent-compatible — useSearchablethe same way as on Laravel when the database component is configured. - Console: register the same command classes as Laravel with Hyperf’s command system (or invoke Symfony
Applicationwith these commands in a custom entry script). - Queues: same as Laravel — without
Webman\RedisQueue, preferqueue=> false or custom async jobs.
- Scope: the
Searchabletrait expects Eloquent (Illuminate\Database\Eloquent\Model) observers and collections. It does not attach tothink\Modelout of the box. - When it works: projects that already use
illuminate/database(or another stack) with real Eloquent models, or a bridge that exposes Laravel-styleconfig()andapp()with the Illuminate container, can follow the Laravel steps: Scout config file +SCOUT_CONFIG_KEY+EngineManagerbinding. - Pure ThinkPHP models: index data by calling
app(EngineManager::class)->engine()(or the concrete engine class)update/delete/searchwith arrays you build yourself, or maintain a thin Eloquent model mapped to the same table for search-only usage. - Config: ThinkPHP’s
config('scout.driver')works if you defineconfig/scout.php(or the version your major version uses) with the same array shape as this package’sapp.php.
| Step | Webman | Laravel | Hyperf | ThinkPHP (Eloquent/hybrid) |
|---|---|---|---|---|
| Scout config file | config/plugin/.../app.php |
config/scout.php |
config/autoload/scout.php |
config/scout.php (or equivalent) |
SCOUT_CONFIG_KEY |
Usually omit | scout |
scout |
scout (if not using plugin path) |
| Console | php webman scout:* |
php artisan scout:* (after registration) |
Hyperf command registration | Per your console setup |
| Async indexing | Redis Queue + consumers | queue false or custom jobs |
queue false or custom jobs |
queue false or custom jobs |
All Scout options are read via scout_config('key'), which respects the resolved config root above.
| Key | Purpose |
|---|---|
driver |
Default engine: opensearch, elasticsearch, meilisearch, typesense, algolia, database, collection, null, … |
prefix |
Index name prefix |
queue |
Enable async indexing (Webman Redis Queue when installed) |
chunk.searchable / chunk.unsearchable |
Chunk sizes for bulk import/remove |
soft_delete |
Keep soft-deleted rows in the index |
'opensearch' => [
'host' => getenv('OPENSEARCH_HTTP_HOST') ?: 'https://127.0.0.1:6205',
'username' => getenv('OPENSEARCH_USERNAME') ?: 'admin',
'password' => getenv('OPENSEARCH_PASSWORD') ?: 'admin',
'prefix' => getenv('OPENSEARCH_INDEX_PREFIX') ?: '',
'ssl_verification' => (bool) (getenv('OPENSEARCH_SSL_VERIFICATION') ?: false),
'indices' => [
'products' => [
'settings' => [ /* ... */ ],
'mappings' => [
'properties' => [
'vector' => ['type' => 'knn_vector', 'dimension' => 1536],
'location' => ['type' => 'geo_point'],
],
],
],
],
],use Erikwang2013\WebmanScout\Searchable;
use support\Model; // Webman; use your Eloquent base otherwise
class Product extends Model
{
use Searchable;
public function searchableAs(): string
{
return 'products';
}
public function toSearchableArray(): array
{
return [
'id' => $this->id,
'title' => $this->title,
'content' => $this->content,
'price' => $this->price,
'created_at' => $this->created_at?->timestamp,
'location' => ['lat' => $this->lat, 'lon' => $this->lng],
'vector' => $this->embedding ?? [],
];
}
public function searchableFields(): array
{
return ['title', 'content'];
}
}// Search
$products = Product::search('phone')->get();
$products = Product::search('phone', function ($builder) {
$builder->where('status', 1);
})->get();
$paginator = Product::search('phone')->paginate(15);
Product::search('keyword')
->where('status', 1)
->whereIn('category_id', [1, 2, 3])
->orderBy('created_at', 'desc')
->limit(20)
->get();
// Indexing
$product->searchableSync();
$product->searchable();
$product->unsearchable();
Product::makeAllSearchable();
Product::removeAllFromSearch();
Product::withoutSyncingToSearch(function () {
Product::query()->where('id', 1)->update(['title' => 'New title']);
});Product::search('')
->whereRange('created_at', ['gte' => 1609459200, 'lte' => 1640995200], true)
->whereRange('price', ['gte' => 100, 'lt' => 500])
->get();
Product::search('')
->whereGeoDistance('location', 31.23, 121.47, 10.0)
->get();
Product::search('')
->fulltextSearch('keyword', ['title', 'content'], ['operator' => 'and'])
->get();
Product::search('')
->orderByVectorSimilarity([0.1, -0.2 /* ... */], 'vector')
->get();
$builder = Product::search('keyword')
->aggregate('price_ranges', 'range', 'price', ['ranges' => [
['from' => 0, 'to' => 100],
['from' => 100, 'to' => 500],
]])
->facet('category_id', ['size' => 10]);
$results = $builder->get();
$aggregations = $builder->getAggregations();
$facets = $builder->getFacets();
$engine = app(\Erikwang2013\WebmanScout\EngineManager::class)->engine();
$engine->updateIndexMappings('products', [
'properties' => [
'new_field' => ['type' => 'keyword'],
],
]);
$builder = Product::search('keyword');
$builder->whereRange('created_at', $range)->get();
$builder->clearAdvancedConditions();On Webman, use php webman …. On Laravel, register the command classes (see Laravel under Framework-specific setup) and run php artisan scout:import, etc.
| Command | Description |
|---|---|
php webman scout:import [Model] |
Full import; --chunk, --fresh |
php webman scout:flush [Model] |
Clear model data from the index |
php webman scout:delete-index [Model] |
Drop index for the model |
php webman scout:index |
List / create indexes (engine-dependent) |
php webman scout:queue-import |
Queue-based import |
php webman scout:sync-index-settings |
Sync index settings |
php webman scout:delete-all-indexes |
Delete all managed indexes (dangerous) |
Use --help on each command for options.
With queue enabled, searchable() / unsearchable() dispatch to Webman Redis Queue when Webman\RedisQueue\Redis is available. Ensure consumers under app/queue/redis/search are running (e.g. scout_make, scout_remove).
| Method | Description |
|---|---|
whereRange($field, array $range, bool $inclusive = true) |
Range filter |
whereGeoDistance($field, $lat, $lng, $radius) |
Geo distance |
fulltextSearch($query, array $fields = [], array $options = []) |
Full-text |
orderByVectorSimilarity(array $vector, ?string $vectorField = null) |
Vector sort |
aggregate(...) / facet(...) |
Aggregations / facets |
addResultProcessor(callable $processor) |
Post-process hits |
getAggregations() / getFacets() |
Read facet/agg results |
clearAdvancedConditions() |
Reset advanced state |
Engines such as OpenSearch expose updateIndexMappings(string $index, array $mappings).
MIT