Download the PHP package matchory/elasticsearch without Composer
On this page you can find all versions of the php package matchory/elasticsearch. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download matchory/elasticsearch
More information about matchory/elasticsearch
Files in matchory/elasticsearch
Package elasticsearch
Short Description The missing elasticsearch ORM for Laravel!
License MIT
Homepage https://www.matchory.com
Informations about the package elasticsearch
Laravel Elasticsearch Integration
This is a fork of the excellent library by @basemkhirat, who sadly seems to have
abandoned it by now.
As we rely on this library quite heavily, we will attempt to keep it up to date and compatible with newer Laravel and
Elasticsearch versions.
Changes in this fork:
- [x] Support for Elasticsearch 7.10 and newer
- [x] Support for PHP 7.3 and newer (PHP 8 included!)
- [x] Broadened support for Laravel libraries, allowing you to use it with almost all versions of Laravel
- [x] Type hints in all supported places, giving confidence in all parameters
- [x] Docblock annotations for advanced autocompletion, extensive inline documentation
- [x] Clean separation of connection management into a
ConnectionManagerclass, while preserving backwards compatibility - [x] Support for most Eloquent model behaviour (see below)
- [x] Removed dependencies on Laravel internals
If you're interested in contributing, please submit a PR or open an issue!
Features:
- Fluent Elasticsearch query builder with an elegant syntax
- Elasticsearch models inspired by Laravel's Eloquent
- Index management using simple artisan commands
- Can be used as a Laravel Scout driver
- Parallel usage of multiple Elasticsearch connections
- Built-in pagination based on Laravel Pagination
- Caching queries using a caching layer based on laravel cache.
Table of Contents
- Requirements
- Installation
- Install package using composer
- Laravel Installation
- Generic app installation
- Install package using composer
- Configuration (Laravel)
- Artisan commands (Laravel)
es:indices:list: List all indices on serveres:indices:create: Create indices defined inconfig/es.phpes:indices:update: Update indices defined inconfig/es.phpes:indices:drop: Drop index- Reindexing data (with zero downtime)
- Usage as a Laravel Scout driver
- Elasticsearch models
- Index Names
- Connection Names
- Mapping type
- Default Attribute Values
- Retrieving Models
- Adding additional constraints
- Collections
- Chunking Results
- Retrieving individual Models
- Not Found Exceptions
- Inserting and Updating Models
- Inserts
- Updates
- Examining Attribute Changes
- Mass Assignment
- Allowing Mass Assignment
- Upserts
- Deleting An Existing Model By Its ID
- Query Scopes
- Global Scopes
- Local Scopes
- Dynamic Scopes
- Comparing Models
- Events
- Replicating Models
- Mutators and Casting
- Usage as a query builder
- Fuzzy Search
- Match Phrase
- Match Phrase Prefix
- Min Score Filtering
- Search After (Deep Pagination)
- Track Total Hits
- Suggesters (Autocomplete)
- Aggregations
- Bulk Update By Query
- Bulk Delete By Query
- Retry Logic
- Query Profiling
- Testing
- Faking Elasticsearch
- Response Builders
- Assertions
- Response Sequences
- Stubbed Responses
- Dynamic Responses
- PHPUnit Trait
- Releases
- Authors
- Bugs, Suggestions and Contributions
- License
Requirements
- PHP >=
8.3 laravel/laravel>= 12.*, or another application using composer
Installation
This section describes the installation process for all supported application types.
Install package using composer
Whether you're using Laravel or another framework, start by installing the package using composer:
Laravel Installation
If you have package autodiscovery disabled, add the service provider and facade to your config/app.php:
Lastly, publish the service provider to your configuration directory:
Generic app installation
You can install package with any composer-based application. While we can't provide general instructions, the following example should give you an idea of how it works:
Configuration (Laravel)
After publishing the service provider, a configuration file has been created at config/elasticsearch.php. Here, you
can add one or more Elasticsearch connections, with multiple servers each. Take a look at the following example:
If you'd like to use Elastic\Elasticsearch with Laravel Scout, you
can find the scout specific settings in
config/scout.php.
Artisan commands (Laravel)
With the artisan commands included with this package, you can create or update settings, mappings and aliases. Note that
all commands use the default connection
by default. You can change this by passing the --connection <your_connection_name> option.
The following commands are available:
es:indices:list: List all indices on server
es:indices:create: Create indices defined in config/es.php
Note that creating operation skips the index if exists.
es:indices:update: Update indices defined in config/es.php
Note that updating operation updates indices setting, aliases and mapping and doesn't delete the indexed data.
es:indices:drop: Drop index
Be careful when using this command, as you will lose your index data!
Running drop command with --force option will skip all confirmation messages.
Reindexing data (with zero downtime)
First, why reindexing?
Changing index mapping doesn't reflect without data reindexing, otherwise your search results will not work on the right
way.
To avoid down time, your application should work with index alias not index name.
The index alias is a constant name that application should work with to avoid change index names.
Assume that we want to change mapping for my_index, this is how to do that:
-
Add
aliasas examplemy_index_aliastomy_indexconfiguration and make sure your application is working with it. -
Update index with command:
-
Create a new index as example
my_new_indexwith your new mapping in configuration file. -
Reindex data from
my_indexintomy_new_indexwith command: - Remove
my_index_aliasalias frommy_indexand add it tomy_new_indexin configuration file and update with command:
Usage as a Laravel Scout driver
First, follow Laravel Scout installation.
All you have to do is updating the following lines in config/scout.php:
Have a look at Laravel Scout documentation, too!
Elasticsearch models
Each index type has a corresponding "Model" which is used to interact with that type. Models allow you to query for data in your types or indices, as well as insert new documents into the type. Elasticsearch Models mimic Eloquent models as closely as possible: You can use model events, route bindings, advanced attribute methods and more. If there is any Eloquent functionality you're missing, open an issue, and we'll be happy to add it!.
Supported features:
- Attributes
- Events
- Route bindings
- Global and Local Query Scopes
- Replicating models
A minimal model might look like this:
Index Names
This model is not specifically bound to any index and will simply use the index configured for the given Elasticsearch
connection. To specifically target an
index, you may define an index property on the model:
Connection Names
By default, all Elasticsearch models will use the default connection that's configured for your application. If you would like to specify a different connection that should be used when interacting with a particular model, you should define a $connection property on the model:
Mapping type
If you're still using mapping types, you may add a type property to your model to indicate the mapping _type to be
used for queries.
Mapping Types are deprecated:
Please note that Elastic has deprecated mapping types and will remove them in the next major release. You should not rely on them to continue working.
Default Attribute Values
By default, a newly instantiated model instance will not contain any attribute values. If you would like to define the
default values for some of your model's
attributes, you may define an attributes property on your model:
Retrieving Models
Once you have created a model and its associated index type, you are ready to start retrieving data from your index. You
can think of your Elasticsearch model
as a powerful query builder allowing you to fluently query the index associated with the model. The model's all method
will retrieve all the documents from
the model's associated Elasticsearch index:
Adding additional constraints
The all method will return all the results in the model's index. However, since each Elasticsearch model serves as a
query builder, you may add additional
constraints to queries, and then invoke the get() method to retrieve the results:
Collections
As we have seen, Elasticsearch methods like all and get retrieve multiple documents from the index. However, these
methods don't return a plain PHP array.
Instead, an instance of Matchory\Elasticsearch\Collection is returned.
The Elasticsearch Collection class extends Laravel's base Illuminate\Support\Collection class, which provides a
variety of helpful methods for interacting with data
collections. For example, the reject
method may be used to remove models from a collection based on the results of an invoked closure:
In addition to the methods provided by Laravel's base collection class, the Elasticsearch collection class provides a few extra methods that are specifically intended for interacting with collections of Elasticsearch models:
Result Meta data
Elasticsearch provides a few additional fields in addition to the hits of a query, like the total result amount, or the query execution time. The Elasticsearch collection provides getters for these properties:
Iterating
Since all of Laravel's collections implement PHP's iterable interfaces, you may loop over collections as if they were
an array:
Chunking Results
Elasticsearch indices can grow quite huge. Your application may run out of memory if you would attempt to load tens of
thousands of Elasticsearch documents via
the all or get methods without an upper bound. Therefore, the default amount of documents fetched is set to 10. To
change this, use the take method:
Retrieving individual Models
In addition to retrieving all the documents matching a given query, you may also retrieve single documents using the
find, first, or firstWhere methods.
Instead of returning a collection of models, these methods return a single model instance:
$posts = Post::ofType('news')->get(); php ES::index("my_index")->where("views", "<", 150)->get(); php ES::index("my_index")->where("views", "<=", 150)->get(); php ES::index("my_index")->where("title", "like", "foo")->get(); php ES::index("my_index")->where("hobbies", "exists", true)->get();
or
ES::index("my_index")->whereExists("hobbies", true)->get(); php ES::index("my_index")->whereIn("id", [100, 150])->get(); php ES::index("my_index")->whereBetween("id", 100, 150)->get();
or
ES::index("my_index")->whereBetween("id", [100, 150])->get(); php ES::index("my_index")->whereNot("status", "published")->get();
or
ES::index("my_index")->whereNot("status", "=", "published")->get(); php ES::index("my_index")->whereNot("views", ">", 150)->get(); php ES::index("my_index")->whereNot("views", ">=", 150)->get(); php ES::index("my_index")->whereNot("views", "<", 150)->get(); php ES::index("my_index")->whereNot("views", "<=", 150)->get(); php ES::index("my_index")->whereNot("title", "like", "foo")->get(); php ES::index("my_index")->whereNot("hobbies", "exists", true)->get();
or
ES::index("my_index")->whereExists("hobbies", true)->get(); php ES::index("my_index")->whereNotIn("id", [100, 150])->get(); php ES::index("my_index")->whereNotBetween("id", 100, 150)->get();
or
ES::index("my_index")->whereNotBetween("id", [100, 150])->get(); php
Find documents where status is "published" OR "featured"
ES::index("my_index") ->orWhere("status", "published") ->orWhere("status", "featured") ->get();
Combine AND and OR conditions
This finds documents where (category = "tech") AND (status = "published" OR status = "featured")
ES::index("my_index") ->where("category", "tech") ->orWhere("status", "published") ->orWhere("status", "featured") ->get();
orWhere supports all the same operators as where
ES::index("my_index")->orWhere("views", ">", 1000)->get(); ES::index("my_index")->orWhere("title", "like", "elasticsearch")->get(); ES::index("my_index")->orWhere("featured", "exists", true)->get(); php
At least 2 of the OR conditions must match
ES::index("my_index") ->orWhere("tag", "php") ->orWhere("tag", "laravel") ->orWhere("tag", "elasticsearch") ->minimumShouldMatch(2) ->get();
Can also use percentages
ES::index("my_index") ->orWhere("tag", "php") ->orWhere("tag", "laravel") ->orWhere("tag", "elasticsearch") ->minimumShouldMatch("75%") ->get(); php ES::index("my_index")->distance("location", ["lat" => -33.8688197, "lon" => 151.20929550000005], "10km")->get();
or
ES::index("my_index")->distance("location", "-33.8688197,151.20929550000005", "10km")->get();
or
ES::index("my_index")->distance("location", [151.20929550000005, -33.8688197], "10km")->get();
php
ES::index("my_index")->body([
"query" => [
"bool" => [
"must" => [
[ "match" => [ "address" => "mill" ] ],
[ "match" => [ "address" => "lane" ] ]
]
]
]
])->get();
Note that you can mix between query builder and array queries.
The query builder will will be merged with the array query.
ES::index("my_index")->body([ "_source" => ["content"]
"query" => [
"bool" => [
"must" => [
[ "match" => [ "address" => "mill" ] ]
]
]
],
"sort" => [
"_score"
]
])->select("name")->orderBy("created_at", "desc")->take(10)->skip(5)->get();
The result query will be
/ Array ( [index] => my_index [type] => my_type [body] => Array ( [_source] => Array ( [0] => content [1] => name ) [query] => Array ( [bool] => Array ( [must] => Array ( [0] => Array ( [match] => Array ( [address] => mill ) ) ) ) ) [sort] => Array ( [0] => _score [1] => Array ( [created_at] => desc ) ) ) [from] => 5 [size] => 10 [client] => Array ( [ignore] => Array ( ) ) ) / php ES::index("my_index")->search("hello")->get();
search with Boost = 2
ES::index("my_index")->search("hello", 2)->get();
search within specific fields with different weights
ES::index("my_index")->search("hello", function($search){ $search->boost(2)->fields(["title" => 2, "content" => 1]) })->get(); php $doc = ES::index("my_index")->highlight("title")->search("hello")->first();
Multiple fields Highlighting is allowed.
$doc = ES::index("my_index")->highlight("title", "content")->search("hello")->first();
Return all highlights as array using $doc->getHighlights() method.
$doc->getHighlights();
Also you can return only highlights of specific field.
$doc->getHighlights("title"); php ES::index("my_index")->search("hello")->first(); php ES::index("my_index")->search("hello")->count(); php
These queries are suitable for large amount of data.
A scrolled search allows you to do an initial search and to keep pulling batches of results
from Elasticsearch until there are no more results left.
It’s a bit like a cursor in a traditional database
$documents = ES::index("my_index")->search("hello") ->scroll("2m") ->take(1000) ->get();
Response will contain a hashed code scroll_id will be used to get the next result by running
$documents = ES::index("my_index")->search("hello") ->scroll("2m") ->scrollID("DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAFMFlJQOEtTdnJIUklhcU1FX2VqS0EwZncAAAAAAAABSxZSUDhLU3ZySFJJYXFNRV9laktBMGZ3AAAAAAAAAU4WUlA4S1N2ckhSSWFxTUVfZWpLQTBmdwAAAAAAAAFPFlJQOEtTdnJIUklhcU1FX2VqS0EwZncAAAAAAAABTRZSUDhLU3ZySFJJYXFNRV9laktBMGZ3") ->get();
And so on ...
Note that you don't need to write the query parameters in every scroll. All you need the scroll_id and query scroll time.
To clear scroll_id
ES::index("my_index")->scrollID("DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAFMFlJQOEtTdnJIUklhcU1FX2VqS0EwZncAAAAAAAABSxZSUDhLU3ZySFJJYXFNRV9laktBMGZ3AAAAAAAAAU4WUlA4S1N2ckhSSWFxTUVfZWpLQTBmdwAAAAAAAAFPFlJQOEtTdnJIUklhcU1FX2VqS0EwZncAAAAAAAABTRZSUDhLU3ZySFJJYXFNRV9laktBMGZ3") ->clear(); php $documents = ES::index("my_index")->search("hello")->paginate(5);
Getting pagination links
$documents->links();
Bootstrap 4 pagination
$documents->links("bootstrap-4");
Simple bootstrap 4 pagination
$documents->links("simple-bootstrap-4");
Simple pagination
$documents->links("simple-default"); php $documents->count() $documents->currentPage() $documents->firstItem() $documents->hasMorePages() $documents->lastItem() $documents->lastPage() $documents->nextPageUrl() $documents->perPage() $documents->previousPageUrl() $documents->total() $documents->url($page) php ES::index("my_index")->search("hello")->where("views", ">", 150)->toArray(); php ES::index("my_index")->search("hello")->where("views", ">", 150)->response(); php ES::index("my_index")->ignore(404, 500)->key(5)->first(); php ES::index("my_index")->search("hello")->remember(10)->get();
Specify a custom cache key
ES::index("my_index")->search("hello")->remember(10, "last_documents")->get();
Caching using other available driver
ES::index("my_index")->search("hello")->cacheDriver("redis")->remember(10, "last_documents")->get();
Caching with cache key prefix
ES::index("my_index")->search("hello")->cacheDriver("redis")->cachePrefix("docs")->remember(10, "last_documents")->get(); php
Basic fuzzy search
ES::index("my_index")->fuzzy("name", "jonh")->get();
With fuzziness control (AUTO, 0, 1, 2)
ES::index("my_index")->fuzzy("name", "jonh", "AUTO")->get();
With prefix length and max expansions
ES::index("my_index")->fuzzy("name", "jonh", "AUTO", prefixLength: 2, maxExpansions: 50)->get(); php
Exact phrase match
ES::index("my_index")->matchPhrase("content", "quick brown fox")->get();
With slop (allows words between)
ES::index("my_index")->matchPhrase("content", "quick fox", slop: 2)->get(); php ES::index("my_index")->matchPhrasePrefix("title", "elastic sea")->get();
With max expansions
ES::index("my_index")->matchPhrasePrefix("title", "elastic sea", maxExpansions: 10)->get(); php ES::index("my_index")->search("hello")->minScore(0.5)->get(); php
First page
$page1 = ES::index("my_index") ->orderBy("created_at", "desc") ->orderBy("_id") ->take(10) ->get();
Get sort values from last document
$lastDoc = $page1->last(); $sortValues = [$lastDoc->created_at, $lastDoc->_id];
Next page using search_after
$page2 = ES::index("my_index") ->orderBy("created_at", "desc") ->orderBy("_id") ->searchAfter($sortValues) ->take(10) ->get(); php
Disable total hit counting (faster for large datasets)
ES::index("my_index")->search("hello")->trackTotalHits(false)->get();
Track up to a specific number
ES::index("my_index")->search("hello")->trackTotalHits(10000)->get();
Track all hits (default)
ES::index("my_index")->search("hello")->trackTotalHits(true)->get(); php
Basic completion suggestion
$results = ES::index("my_index") ->suggest("title-suggest", "ela", "title.suggest") ->get();
Get suggestions from results
$suggestions = $results->getSuggestions();
With size limit
ES::index("my_index") ->suggest("title-suggest", "ela", "title.suggest", size: 10) ->get();
With fuzzy matching
ES::index("my_index") ->suggest("title-suggest", "ela", "title.suggest", fuzzy: "AUTO") ->get();
With context filtering
ES::index("my_index") ->suggest("title-suggest", "ela", "title.suggest", contexts: [ "category" => ["tech", "science"] ]) ->get();
Skip duplicate suggestions
ES::index("my_index") ->suggest("title-suggest", "ela", "title.suggest", skipDuplicates: true) ->get(); php $results = ES::index("my_index") ->suggestTerm("spell-check", "helo wrld", "content") ->get();
$suggestions = $results->getSuggestions(); php
Average
ES::index("my_index")->avgAgg("avg_price", "price")->get();
Sum
ES::index("my_index")->sumAgg("total_sales", "amount")->get();
Min
ES::index("my_index")->minAgg("min_price", "price")->get();
Max
ES::index("my_index")->maxAgg("max_price", "price")->get();
Cardinality (distinct count)
ES::index("my_index")->cardinalityAgg("unique_users", "user_id")->get();
Value count
ES::index("my_index")->valueCountAgg("doc_count", "status")->get();
Stats (min, max, sum, count, avg)
ES::index("my_index")->statsAgg("price_stats", "price")->get(); php
Terms aggregation
ES::index("my_index")->termsAgg("categories", "category", size: 20)->get();
Date histogram
ES::index("my_index") ->dateHistogramAgg("sales_over_time", "created_at", "month", format: "yyyy-MM") ->get();
Numeric histogram
ES::index("my_index")->histogramAgg("price_ranges", "price", interval: 50)->get();
Range aggregation
ES::index("my_index")->rangeAgg("price_buckets", "price", [ ["to" => 50], ["from" => 50, "to" => 100], ["from" => 100] ])->get();
Filter aggregation
ES::index("my_index")->filterAgg("active_users", [ "term" => ["status" => "active"] ])->get(); php
Add sub-aggregation before the parent
ES::index("my_index") ->subAgg("categories", "avg_price", ["avg" => ["field" => "price"]]) ->termsAgg("categories", "category") ->get();
Multiple sub-aggregations
ES::index("my_index") ->subAgg("categories", "avg_price", ["avg" => ["field" => "price"]]) ->subAgg("categories", "max_price", ["max" => ["field" => "price"]]) ->termsAgg("categories", "category") ->get(); php
Update with script
ES::index("my_index") ->where("status", "old") ->updateByQuery("ctx._source.status = 'archived'");
With parameters
ES::index("my_index") ->where("status", "pending") ->updateByQuery("ctx._source.status = params.new_status", [ "new_status" => "processed" ]);
Complex script
ES::index("my_index") ->where("views", ">", 1000) ->updateByQuery([ "source" => "ctx._source.popular = true; ctx._source.views_bucket = 'high'", "lang" => "painless" ]);
Async execution (returns task ID)
$task = ES::index("my_index") ->where("status", "old") ->updateByQuery("ctx._source.status = 'archived'", waitForCompletion: false); php
Delete matching documents
ES::index("my_index") ->where("status", "deleted") ->deleteByQuery();
Async execution (returns task ID)
$task = ES::index("my_index") ->where("created_at", "<", "2020-01-01") ->deleteByQuery(waitForCompletion: false); php
Retry up to 3 times with 100ms initial delay
ES::index("my_index") ->retry(3, 100) ->search("hello") ->get(); php $results = ES::index("my_index") ->profile() ->search("hello") ->get();
Access profiling information from the raw response
$response = ES::index("my_index") ->profile() ->search("hello") ->response();
$profileData = $response['profile']; php ES::raw()->search([ "index" => "my_index", "type" => "my_type", "body" => [ "query" => [ "bool" => [ "must" => [ [ "match" => [ "address" => "mill" ] ], [ "match" => [ "address" => "lane" ] ] ] ] ] ] ]); php ES::index("my_index")->key(3)->insert([ "title" => "Test document", "content" => "Sample content" ]);
A new document will be inserted with _id = 3.
[id is optional] if not specified, a unique hash key will be generated.
php
Main query
ES::index("my_index")->bulk(function ($bulk){
# Sub queries
$bulk->index("my_index_1")->key(10)->insert(["title" => "Test document 1","content" => "Sample content 1"]);
$bulk->index("my_index_2")->key(11)->insert(["title" => "Test document 2","content" => "Sample content 2"]);
$bulk->key(12)->insert(["title" => "Test document 3", "content" => "Sample content 3"]);
});
Notes from the above query:
Index names are extendable. This means that:
If index() is not specified in subquery:
-- The builder will get index name from the main query.
-- If index is not specified in main query, the builder will get index name from configuration file.
You can also use the array-based bulk code style using multidimensional array of [id => data] pairs
ES::index("my_index")->bulk([
10 => [
"title" => "Test document 1",
"content" => "Sample content 1"
],
11 => [
"title" => "Test document 2",
"content" => "Sample content 2"
]
]);
The two given documents will be inserted with its associated ids
php ES::index("my_index")->key(3)->update([ "title" => "Test document", "content" => "sample content" ]);
Document has _id = 3 will be updated.
[id is required]
php
Bulk update
ES::index("my_index")->bulk(function ($bulk){ $bulk->key(10)->update(["title" => "Test document 1","content" => "Sample content 1"]); $bulk->key(11)->update(["title" => "Test document 2","content" => "Sample content 2"]); }); php ES::index("my_index")->key(3)->increment("views");
Document has _id = 3 will be incremented by 1.
ES::index("my_index")->key(3)->increment("views", 3);
Document has _id = 3 will be incremented by 3.
[id is required]
php ES::index("my_index")->key(3)->decrement("views");
Document has _id = 3 will be decremented by 1.
ES::index("my_index")->key(3)->decrement("views", 3);
Document has _id = 3 will be decremented by 3.
[id is required]
php
increment field by script
ES::index("my_index")->key(3)->script( "ctx._source.$field += params.count", ["count" => 1] );
add php tag to tags array list
ES::index("my_index")->key(3)->script( "ctx._source.tags.add(params.tag)", ["tag" => "php"] );
delete the doc if the tags field contain mongodb, otherwise it does nothing (noop)
ES::index("my_index")->key(3)->script( "if (ctx._source.tags.contains(params.tag)) { ctx.op = 'delete' } else { ctx.op = 'none' }", ["tag" => "mongodb"] ); php ES::index("my_index")->key(3)->delete();
Document has _id = 3 will be deleted.
[id is required]
php
Bulk delete
ES::index("my_index")->bulk(function ($bulk){ $bulk->key(10)->delete(); $bulk->key(11)->delete(); }); php use Matchory\Elasticsearch\Facades\Elasticsearch; use Matchory\Elasticsearch\Testing\Responses\SearchResponse;
$fake = Elasticsearch::fake( SearchResponse::fromDocuments([ ['title' => 'Widget', 'price' => 9.99], ['title' => 'Gadget', 'price' => 19.99], ])->toArray(), );
// Your application code runs normally: $results = Product::where('category', 'electronics')->get();
$this->assertCount(2, $results); $this->assertSame('Widget', $results[0]->title);
// When done, restore the real client: Elasticsearch::unfake(); php use Matchory\Elasticsearch\Testing\Responses\SearchResponse;
// From plain document arrays (most common): SearchResponse::fromDocuments([ ['title' => 'Post 1', 'status' => 'published'], ['title' => 'Post 2', 'status' => 'draft'], ])->toArray();
// With a custom index name: SearchResponse::fromDocuments($docs, 'my_index')->toArray();
// Empty results: SearchResponse::empty()->toArray();
// From raw hit arrays (when you need control over _id, _score, etc.): SearchResponse::make([ ['_index' => 'posts', '_id' => 'abc', '_score' => 2.5, '_source' => ['title' => 'Post']], ])->toArray();
// Override total count (e.g., for pagination): SearchResponse::make($hits, total: 1000)->toArray();
// With aggregations or suggestions: SearchResponse::fromDocuments($docs) ->withAggregations(['status' => ['buckets' => [['key' => 'published', 'doc_count' => 10]]]]) ->withSuggestions(['title_suggest' => [['text' => 'test', 'options' => []]]]) ->toArray(); php use Matchory\Elasticsearch\Testing\Responses\CountResponse;
CountResponse::make(42)->toArray(); php use Matchory\Elasticsearch\Testing\Responses\IndexResponse;
IndexResponse::created('doc-1', 'my_index')->toArray(); IndexResponse::updated('doc-1', 'my_index')->toArray(); IndexResponse::deleted('doc-1', 'my_index')->toArray(); php use Matchory\Elasticsearch\Testing\Responses\BulkResponse;
BulkResponse::allSuccessful(count: 5, index: 'my_index')->toArray(); BulkResponse::make(items: [...], errors: true)->toArray(); php $response = SearchResponse::empty()->merge(['took' => 500]); php $fake = Elasticsearch::fake( SearchResponse::empty()->toArray(), );
Product::where('status', 'active')->get();
// Assert a method was called: $fake->assertSent('search');
// Assert with parameter matching: $fake->assertSent('search', fn(array $params) => $params['index'] === 'products' );
// Assert a method was NOT called: $fake->assertNotSent('index');
// Assert nothing was sent at all: // $fake->assertNothingSent();
// Assert exact call count: $fake->assertSentCount('search', 1);
// Access raw recorded calls for custom inspection: $calls = $fake->recorded('search'); // array of param arrays $all = $fake->recorded(); // all calls keyed by method php use Matchory\Elasticsearch\Testing\ResponseSequence; use Matchory\Elasticsearch\Testing\Responses\SearchResponse;
$fake = Elasticsearch::fake( new ResponseSequence( SearchResponse::fromDocuments([['title' => 'First page']]), SearchResponse::fromDocuments([['title' => 'Second page']]), SearchResponse::empty(), ), );
Product::all(); // Returns "First page" Product::all(); // Returns "Second page" Product::all(); // Returns empty results php $sequence = new ResponseSequence( SearchResponse::fromDocuments([['title' => 'Only first call']]), ); $sequence->whenEmpty(SearchResponse::empty()); php $fake = Elasticsearch::fake();
$fake->getClient()->stubMethod('search', SearchResponse::fromDocuments([['title' => 'Always this']])->toArray() );
$fake->getClient()->stubMethod('count', CountResponse::make(99)->toArray() );
// Every search() call returns the same response Product::all(); // "Always this" Product::all(); // "Always this" php $fake = Elasticsearch::fake( fn(string $method, array $params) => match ($method) { 'search' => SearchResponse::fromDocuments([ ['title' => 'Result for: ' . ($params['index'] ?? 'unknown')], ])->toArray(), 'count' => CountResponse::make(42)->toArray(), default => [], }, ); php namespace Tests\Feature;
use Matchory\Elasticsearch\Testing\FakesElasticsearch; use Matchory\Elasticsearch\Testing\Responses\SearchResponse; use Tests\TestCase;
class ProductSearchTest extends TestCase { use FakesElasticsearch;
public function test_search_returns_results(): void
{
$this->fakeElasticsearch(
SearchResponse::fromDocuments([
['title' => 'Widget', 'price' => 9.99],
])->toArray(),
);
$response = $this->get('/api/products?q=widget');
$response->assertOk();
$response->assertJsonCount(1, 'data');
}
public function test_empty_search(): void
{
$this->fakeElasticsearch(
SearchResponse::empty()->toArray(),
);
$response = $this->get('/api/products?q=nothing');
$response->assertOk();
$response->assertJsonCount(0, 'data');
}
// No manual cleanup needed -- unfake() is called automatically after each test
}
Releases
--------
See the [release page](https://github.com/matchory/elasticsearch/releases).
Authors
-------
[Basem Khirat](http://basemkhirat.com) - [[email protected]](mailto:[email protected]) - [@basemkhirat](https://twitter.com/basemkhirat)
[Moritz Friedrich](https://www.matchory.com) - [[email protected]](mailto:[email protected])
Bugs, Suggestions and Contributions
-----------------------------------
Thanks to [everyone](https://github.com/basemkhirat/elasticsearch/graphs/contributors) who has contributed to the
original project and
[everyone else](https://github.com/matchory/elasticsearch/graphs/contributors) who has contributed to this fork!
Please use [Github](https://github.com/matchory/elasticsearch) for reporting bugs, and making comments or suggestions.
If you're interested in helping out, the most pressing issues would be modernizing the query builder to provide better
support for Elasticsearch features as
well as completing the test suite!
License
-------
MIT
`Have a happy searching..`All versions of elasticsearch with dependencies
ext-json Version *
elasticsearch/elasticsearch Version ^9.0
illuminate/pagination Version ^v12.49.0
illuminate/support Version ^v12.49.0
monolog/monolog Version *
symfony/var-dumper Version *