Scout-style API
search, where, orderBy, paginate; migration is mostly a trait swap.
Scout / Eloquent + Rust + Go
Nine engines, six frameworks plus plain PHP, all behind one query and indexing API.
The Searchable trait wires the model, observer and query builder together; every search request ends at the shared Engine contract.
The config root resolves from the Webman plugin path, SCOUT_CONFIG_KEY, Yii params or Scout::configure(), so the same code runs on six frameworks and plain PHP.
Sniffy, a scout dog with a magnifier: it sniffs out documents, buries them in the index and tracks them back on demand.
One sniff, and documents line up.Only one contract sits between models, the builder and engines.
search, where, orderBy, paginate; migration is mostly a trait swap.
OpenSearch, Elasticsearch, Meilisearch and six more engines.
Time ranges, geo distance, full text, KNN vector ranking and facet aggregations.
saved, deleted and restored events trigger writes, with soft-deleted documents.
Async with Webman Redis Queue; logs and falls back to sync when missing.
Webman, Laravel, Hyperf, ThinkPHP, Yii2, Yii3 and plain PHP.
Beyond PHP, the same search capability ships as Rust and Go implementations.
Search abstraction for Rust
Eight backends behind one Engine trait, zero-dependency in-memory default, no app changes.
Scout-style sync for Go
Syncs model saves, deletes and restores to search, with nine engines and sixteen driver names.
Version entries come from real commits and releases.
Supports Laravel 13 and symfony/console 8, keeping dependency resolution current.
Fixes installation failures and fatal errors under symfony/console 8.x.
Adds a plain PHP configuration entry and the project pet, plus a review round of fixes.
Adds multi-framework integration support and the matching documentation.
Click an image to enlarge.
Install first, then run a minimal example.
composer require erikwang2013/webman-scoutuse Erikwang2013\WebmanScout\Searchable;
use support\Model;
class Product extends Model
{
use Searchable;
public function searchableAs(): string
{
return 'products';
}
}
$products = Product::search('phone')
->where('status', 1)
->paginate(15);cargo add rust-scout
cargo add tokio --features macros,rt
cargo add serde_jsonuse rust_scout::{Engine, EngineManager, ScoutConfig, SearchBuilder, SearchDocument};
#[tokio::main]
async fn main() -> rust_scout::Result<()> {
let engine = EngineManager::new(ScoutConfig::collection()).engine()?;
let doc = SearchDocument::new("book-1", serde_json::json!({
"title": "Programming Rust"
}))?;
engine.update(&[doc]).await?;
let result = engine
.search(SearchBuilder::new("rust").within("default").take(10))
.await?;
println!("total = {}", result.total);
Ok(())
}go get github.com/erikwang2013/go-scouttype Post struct { ID int; Title string }
func (p Post) ScoutKey() any { return p.ID }
func (p Post) TableName() string { return "posts" }
func (p Post) ShouldBeSearchable() bool { return p.Title != "" }
func (p Post) ToSearchableArray() map[string]any { return map[string]any{"title": p.Title} }
func main() {
ctx := context.Background()
s := scout.NewWithConfig(scout.DefaultConfig())
engines.Register(s.Manager)
sr := s.Searchable(&Post{}, scout.NewMemorySource(Post{ID: 1}))
_, _ = sr.MakeAllSearchable(ctx, 0)
_, _ = sr.Search(ctx, "Go", nil).Take(10).Get(ctx)
}Hover for a moment and its mood changes.