{"id":15287984,"url":"https://github.com/matchory/elasticsearch","last_synced_at":"2025-10-07T02:31:59.967Z","repository":{"id":36954725,"uuid":"295747955","full_name":"matchory/elasticsearch","owner":"matchory","description":"The missing Elasticsearch ORM for Laravel","archived":false,"fork":true,"pushed_at":"2024-08-15T09:36:08.000Z","size":2001,"stargazers_count":31,"open_issues_count":14,"forks_count":13,"subscribers_count":4,"default_branch":"3.x","last_synced_at":"2025-01-17T22:47:30.115Z","etag":null,"topics":["caching","elasticsearch","elasticsearch-client","eloquent","indexing","laravel","laravel-package","lumen","orm","php","query-builder","scout","search-engine"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"basemkhirat/elasticsearch","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/matchory.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2020-09-15T14:07:10.000Z","updated_at":"2024-08-02T07:25:39.000Z","dependencies_parsed_at":"2023-10-03T12:34:40.078Z","dependency_job_id":"8f39ab6c-902b-4288-9aeb-6c81fcee77fc","html_url":"https://github.com/matchory/elasticsearch","commit_stats":{"total_commits":492,"total_committers":20,"mean_commits":24.6,"dds":0.5284552845528455,"last_synced_commit":"d47c5fbce5b8334e454bed034fc4a7524dcf2d04"},"previous_names":[],"tags_count":93,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matchory%2Felasticsearch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matchory%2Felasticsearch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matchory%2Felasticsearch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/matchory%2Felasticsearch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/matchory","download_url":"https://codeload.github.com/matchory/elasticsearch/tar.gz/refs/heads/3.x","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235581533,"owners_count":19013089,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["caching","elasticsearch","elasticsearch-client","eloquent","indexing","laravel","laravel-package","lumen","orm","php","query-builder","scout","search-engine"],"created_at":"2024-09-30T15:43:40.537Z","updated_at":"2025-10-07T02:31:59.959Z","avatar_url":"https://github.com/matchory.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Latest Stable Version](https://poser.pugx.org/matchory/elasticsearch/v)](https://packagist.org/packages/matchory/elasticsearch) [![Total Downloads](https://poser.pugx.org/matchory/elasticsearch/downloads)](https://packagist.org/packages/matchory/elasticsearch) [![Latest Unstable Version](https://poser.pugx.org/matchory/elasticsearch/v/unstable)](https://packagist.org/packages/matchory/elasticsearch) [![License](https://poser.pugx.org/matchory/elasticsearch/license)](https://packagist.org/packages/matchory/elasticsearch)\n\nLaravel Elasticsearch Integration\n=================================\nThis is a fork of the excellent library by [@basemkhirat](https://github.com/basemkhirat), who sadly seems to have\nabandoned it by now.  \nAs we rely on this library quite heavily, we will attempt to keep it up to date and compatible with newer Laravel and\nElasticsearch versions.\n\n**Changes in this fork:**\n\n- [x] Support for Elasticsearch 7.10 and newer\n- [x] Support for PHP 7.3 and newer (PHP 8 included!)\n- [x] Broadened support for Laravel libraries, allowing you to use it with almost all versions of Laravel\n- [x] Type hints in all supported places, giving confidence in all parameters\n- [x] Docblock annotations for advanced autocompletion, extensive inline documentation\n- [x] Clean separation of connection management into a [`ConnectionManager` class](./src/ConnectionManager.php), while\n  preserving backwards compatibility\n- [x] Support for _most_ Eloquent model behaviour ([see below](#elasticsearch-models))\n- [x] Removed dependencies on Laravel internals\n\nIf you're interested in contributing, please submit a PR or open an issue!\n\n**Features:**\n\n- Fluent Elasticsearch query builder with an elegant syntax\n- Elasticsearch models inspired by Laravel's Eloquent\n- Index management using simple artisan commands\n- Can be used as a [Laravel Scout](https://laravel.com/docs/8.x/scout) driver\n- Parallel usage of multiple Elasticsearch connections\n- Built-in pagination based on [Laravel Pagination](https://laravel.com/docs/8.x/pagination)\n- Caching queries using a caching layer based on [laravel cache](https://laravel.com/docs/8.x/cache).\n\n**Table of Contents**\n\n- [Requirements](#requirements)\n- [Installation](#installation)\n    * [Install package using composer](#install-package-using-composer)\n        + [Laravel Installation](#laravel-installation)\n    * [Generic app installation](#generic-app-installation)\n- [Configuration (Laravel)](#configuration-laravel)\n- [Artisan commands (Laravel)](#artisan-commands-laravel)\n    * [`es:indices:list`: List all indices on server](#es-indices-list-list-all-indices-on-server)\n    * [`es:indices:create`: Create indices defined in\n      `config/es.php`](#es-indices-create-create-indices-defined-in-config-esphp)\n    * [`es:indices:update`: Update indices defined in\n      `config/es.php`](#es-indices-update-update-indices-defined-in--config-esphp)\n    * [`es:indices:drop`: Drop index](#es-indices-drop-drop-index)\n    * [Reindexing data (with zero downtime)](#reindexing-data-with-zero-downtime)\n- [Usage as a Laravel Scout driver](#usage-as-a-laravel-scout-driver)\n- [Elasticsearch models](#elasticsearch-models)\n    * [Index Names](#index-names)\n    * [Connection Names](#connection-names)\n    * [Mapping type](#mapping-type)\n    * [Default Attribute Values](#default-attribute-values)\n    * [Retrieving Models](#retrieving-models)\n    * [Adding additional constraints](#adding-additional-constraints)\n    * [Collections](#collections)\n    * [Chunking Results](#chunking-results)\n    * [Retrieving individual Models](#retrieving-individual-models)\n    * [Not Found Exceptions](#not-found-exceptions)\n    * [Inserting and Updating Models](#inserting-and-updating-models)\n        + [Inserts](#inserts)\n        + [Updates](#updates)\n        + [Examining Attribute Changes](#examining-attribute-changes)\n        + [Mass Assignment](#mass-assignment)\n        + [Allowing Mass Assignment](#allowing-mass-assignment)\n        + [Upserts](#upserts)\n        + [Deleting An Existing Model By Its ID](#deleting-an-existing-model-by-its-id)\n    * [Query Scopes](#query-scopes)\n        + [Global Scopes](#global-scopes)\n        + [Local Scopes](#local-scopes)\n        + [Dynamic Scopes](#dynamic-scopes)\n    * [Comparing Models](#comparing-models)\n    * [Events](#events)\n    * [Replicating Models](#replicating-models)\n    * [Mutators and Casting](#mutators-and-casting)\n- [Usage as a query builder](#usage-as-a-query-builder)\n- [Releases](#releases)\n- [Authors](#authors)\n- [Bugs, Suggestions and Contributions](#bugs-suggestions-and-contributions)\n- [License](#license)\n\nRequirements\n------------\n\n- PHP \u003e= `8.3`\n- `laravel/laravel` \u003e= 12.*, or another application using composer\n\nInstallation\n------------\nThis section describes the installation process for all supported application types.\n\n### Install package using composer\n\nWhether you're using Laravel or another framework, start by installing the package using composer:\n\n```bash\ncomposer require matchory/elasticsearch\n```\n\n#### Laravel Installation\n\nIf you have package autodiscovery disabled, add the service provider and facade to your `config/app.php`:\n\n```php\n    'providers' =\u003e [\n        // ...\n\n        Matchory\\Elasticsearch\\ElasticsearchServiceProvider::class,\n\n        // ...\n    ],\n\n    // ...\n\n    'aliases' =\u003e [\n        // ...\n\n        'ES' =\u003e Matchory\\Elasticsearch\\Facades\\ES::class,\n\n        // ...\n    ],\n```\n\nLastly, publish the service provider to your configuration directory:\n\n```bash\nphp artisan vendor:publish --provider=\"Matchory\\Elasticsearch\\ElasticsearchServiceProvider\"\n```\n\n### Generic app installation\n\nYou can install package with any composer-based application. While we can't provide general instructions, the following\nexample should give you an idea of how\nit works:\n\n```php\nrequire \"vendor/autoload.php\";\n\nuse Matchory\\Elasticsearch\\ConnectionManager;\nuse Matchory\\Elasticsearch\\Factories\\ClientFactory;\n\n$connectionManager = new ConnectionManager([\n 'servers' =\u003e [\n        [\n            \"host\" =\u003e '127.0.0.1',\n            \"port\" =\u003e 9200,\n            'user' =\u003e '',\n            'pass' =\u003e '',\n            'scheme' =\u003e 'http',\n        ],\n    ],\n\n\t// Custom handlers\n\t// 'handler' =\u003e new MyCustomHandler(),\n\n    'index' =\u003e 'my_index',\n], new ClientFactory());\n\n$connection = $connectionManager-\u003econnection();\n\n// Access the query builder using created connection\n$documents = $connection-\u003esearch(\"hello\")-\u003eget();\n```\n\nConfiguration (Laravel)\n-----------------------\nAfter publishing the service provider, a configuration file has been created at `config/elasticsearch.php`. Here, you\ncan add one or more Elasticsearch connections, with multiple servers each. Take a look at the following example:\n\n```php\n# Here you can define the default connection name.\n'default' =\u003e env('ELASTIC_CONNECTION', 'default'),\n\n# Here you can define your connections.\n'connections' =\u003e [\n\t'default' =\u003e [\n\t    'servers' =\u003e [\n\t        [\n\t            \"host\" =\u003e env(\"ELASTIC_HOST\", \"127.0.0.1\"),\n\t            \"port\" =\u003e env(\"ELASTIC_PORT\", 9200),\n\t            'user' =\u003e env('ELASTIC_USER', ''),\n\t            'pass' =\u003e env('ELASTIC_PASS', ''),\n\t            'scheme' =\u003e env('ELASTIC_SCHEME', 'http'),\n\t        ]\n\t    ],\n\t    \n\t\t// Custom handlers\n\t\t// 'handler' =\u003e new MyCustomHandler(),\n\t\t'index' =\u003e env('ELASTIC_INDEX', 'my_index')\n\t]\n],\n \n# Here you can define your indices.\n'indices' =\u003e [\n\t'my_index_1' =\u003e [\n\t    \"aliases\" =\u003e [\n\t        \"my_index\"\n\t    ],\n\t    'settings' =\u003e [\n\t        \"number_of_shards\" =\u003e 1,\n\t        \"number_of_replicas\" =\u003e 0,\n\t    ],\n\t    'mappings' =\u003e [\n\t        'posts' =\u003e [\n                'properties' =\u003e [\n                    'title' =\u003e [\n                        'type' =\u003e 'string'\n                    ]\n                ]\n\t        ]\n\t    ]\n\t]\n]\n```\n\nIf you'd like to use Elastic\\Elasticsearch with [Laravel Scout](https://laravel.com/docs/8.x/scout#introduction), you\ncan find the scout specific settings in\n`config/scout.php`.\n\nArtisan commands (Laravel)\n--------------------------\nWith the artisan commands included with this package, you can create or update settings, mappings and aliases. Note that\nall commands use the default connection\nby default. You can change this by passing the `--connection \u003cyour_connection_name\u003e` option.\n\nThe following commands are available:\n\n### `es:indices:list`: List all indices on server\n\n```bash-\n$ php artisan es:indices:list\n+----------------------+--------+--------+----------+------------------------+-----+-----+------------+--------------+------------+----------------+\n| configured (es.php)  | health | status | index    | uuid                   | pri | rep | docs.count | docs.deleted | store.size | pri.store.size |\n+----------------------+--------+--------+----------+------------------------+-----+-----+------------+--------------+------------+----------------+\n| yes                  | green  | open   | my_index | 5URW60KJQNionAJgL6Q2TQ | 1   | 0   | 0          | 0            | 260b       | 260b           |\n+----------------------+--------+--------+----------+------------------------+-----+-----+------------+--------------+------------+----------------+\n```\n\n### `es:indices:create`: Create indices defined in `config/es.php`\n\nNote that creating operation skips the index if exists.\n\n```bash\n# Create all indices in config file.\nphp artisan es:indices:create\n\n# Create only 'my_index' index in config file\nphp artisan es:indices:create my_index \n```\n\n### `es:indices:update`: Update indices defined in `config/es.php`\n\nNote that updating operation updates indices setting, aliases and mapping and doesn't delete the indexed data.\n\n```bash\n# Update all indices in config file.\nphp artisan es:indices:update\n\n# Update only 'my_index' index in config file\nphp artisan es:indices:update my_index \n```\n\n### `es:indices:drop`: Drop index\n\n**Be careful when using this command, as you will lose your index data!**  \nRunning drop command with `--force` option will skip all confirmation messages.\n\n```bash\n# Drop all indices in config file.\nphp artisan es:indices:drop\n\n# Drop specific index on sever. Not matter for index to be exist in config file or not.\nphp artisan es:indices:drop my_index \n```\n\n### Reindexing data (with zero downtime)\n\n**First, why reindexing?**  \nChanging index mapping doesn't reflect without data reindexing, otherwise your search results will not work on the right\nway.  \nTo avoid down time, your application should work with index `alias` not index `name`.  \nThe index `alias` is a constant name that application should work with to avoid change index names.\n\n**Assume that we want to change mapping for `my_index`, this is how to do that:**\n\n1. Add `alias` as example `my_index_alias` to `my_index` configuration and make sure your application is working with\n   it.\n   ```php\n   \"aliases\" =\u003e [\n       \"my_index_alias\"\n   ]       \n   ```\n\n2. Update index with command:\n   ```bash\n   php artisan es:indices:update my_index\n   ```\n\n3. Create a new index as example `my_new_index` with your new mapping in configuration file.\n   ```bash\n   $ php artisan es:indices:create my_new_index\n   ```\n\n4. Reindex data from `my_index` into `my_new_index` with command:\n   ```bash\n   php artisan es:indices:reindex my_index my_new_index\n   \n   # Control bulk size. Adjust it with your server.\n   php artisan es:indices:reindex my_index my_new_index --bulk-size=2000\n   \n   # Control query scroll value.\n   php artisan es:indices:reindex my_index my_new_index --bulk-size=2000 --scroll=2m\n   \n   # Skip reindexing errors such as mapper parsing exceptions.\n   php artisan es:indices:reindex my_index my_new_index --bulk-size=2000 --skip-errors \n   \n   # Hide all reindexing errors and show the progres bar only.\n   php artisan es:indices:reindex my_index my_new_index --bulk-size=2000 --skip-errors --hide-errors\n   ```\n\n5. Remove `my_index_alias` alias from `my_index` and add it to `my_new_index` in configuration file and update with\n   command:\n   ```bash\n   php artisan es:indices:update\n   ```\n\nUsage as a Laravel Scout driver\n-------------------------------\nFirst, follow [Laravel Scout installation](https://laravel.com/docs/8.0/scout#installation).  \nAll you have to do is updating the following lines in `config/scout.php`:\n\n```php\n# change the default driver to 'elasticsearch'\n'driver' =\u003e env('SCOUT_DRIVER', 'elasticsearch'),\n\n# link `elasticsearch` driver with default elasticsearch connection in config/es.php\n'elasticsearch' =\u003e [\n    'connection' =\u003e env('ELASTIC_CONNECTION', 'default'),\n],\n```\n\nHave a look at [Laravel Scout documentation](https://laravel.com/docs/8.0/scout#configuration), too!\n\nElasticsearch models\n--------------------\nEach index type has a corresponding _\"Model\"_ which is used to interact with that type. Models allow you to query for\ndata in your types or indices, as well as\ninsert new documents into the type. Elasticsearch Models mimic Eloquent models as closely as possible: You can use model\nevents, route bindings, advanced\nattribute methods and more. **If there is any Eloquent functionality you're missing, open an issue, and we'll be happy\nto add it!**.\n\n\u003e **Supported features:**\n\u003e  - Attributes\n\u003e  - Events\n\u003e  - Route bindings\n\u003e  - Global and Local Query Scopes\n\u003e  - Replicating models\n\nA minimal model might look like this:\n\n```php\nnamespace App\\Models;\n\nuse Matchory\\Elasticsearch\\Model;\n\nclass Post extends Model\n{\n    // ...\n}\n```\n\n### Index Names\n\nThis model is not specifically bound to any index and will simply use the index configured for the given Elasticsearch\nconnection. To specifically target an\nindex, you may define an `index` property on the model:\n\n```php\nnamespace App\\Models;\n\nuse Matchory\\Elasticsearch\\Model;\n\nclass Post extends Model\n{\n    protected $index = 'posts';\n}\n```\n\n### Connection Names\n\nBy default, all Elasticsearch models will use the default connection that's configured for your application. If you\nwould like to specify a different connection\nthat should be used when interacting with a particular model, you should define a $connection property on the model:\n\n```php\nnamespace App\\Models;\n\nuse Matchory\\Elasticsearch\\Model;\n\nclass Post extends Model\n{\n    protected $connection = 'blag';\n}\n```\n\n### Mapping type\n\nIf you're still using mapping types, you may add a `type` property to your model to indicate the mapping `_type` to be\nused for queries.\n\n\u003e **Mapping Types are deprecated:**  \n\u003e Please note that Elastic\n\u003e has [deprecated mapping types](https://www.elastic.co/guide/en/elasticsearch/reference/current/removal-of-types.html)\n\u003e and will remove\n\u003e them in the next major release. You should not rely on them to continue working.\n\n```php\nnamespace App;\n\nuse Matchory\\Elasticsearch\\Model;\n\nclass Post extends Model\n{\n    protected $type = 'posts';\n}\n```\n\n### Default Attribute Values\n\nBy default, a newly instantiated model instance will not contain any attribute values. If you would like to define the\ndefault values for some of your model's\nattributes, you may define an `attributes` property on your model:\n\n```php\nnamespace App\\Models;\n\nuse Matchory\\Elasticsearch\\Model;\n\nclass Post extends Model\n{\n    protected $attributes = [\n        'published' =\u003e false,\n    ];\n}\n```\n\n### Retrieving Models\n\nOnce you have created a model and its associated index type, you are ready to start retrieving data from your index. You\ncan think of your Elasticsearch model\nas a powerful query builder allowing you to fluently query the index associated with the model. The model's `all` method\nwill retrieve all the documents from\nthe model's associated Elasticsearch index:\n\n```php\nuse App\\Models\\Post;\n\nforeach (Post::all() as $post) {\n    echo $post-\u003etitle;\n}\n```\n\n### Adding additional constraints\n\nThe `all` method will return all the results in the model's index. However, since each Elasticsearch model serves as a\nquery builder, you may add additional\nconstraints to queries, and then invoke the `get()` method to retrieve the results:\n\n```php\nuse App\\Models\\Post;\n\n$posts = Post::where('status', 1)\n             -\u003eorderBy('created_at', 'desc')\n             -\u003etake(10)\n             -\u003eget();\n```\n\n### Collections\n\nAs we have seen, Elasticsearch methods like `all` and `get` retrieve multiple documents from the index. However, these\nmethods don't return a plain PHP array.\nInstead, an instance of [`Matchory\\Elasticsearch\\Collection`](./src/Collection.php) is returned.\n\nThe Elasticsearch `Collection` class extends Laravel's base `Illuminate\\Support\\Collection` class, which provides a\n[variety of helpful methods](https://laravel.com/docs/master/collections#available-methods) for interacting with data\ncollections. For example, the `reject`\nmethod may be used to remove models from a collection based on the results of an invoked closure:\n\n```php\nuse App\\Models\\Post;\n\n$posts = Post::where('sponsored', true)-\u003eget();\n$posts = $posts-\u003ereject($post =\u003e $post-\u003ein_review);\n```\n\nIn addition to the methods provided by Laravel's base collection class, the Elasticsearch collection class provides a\nfew extra methods that are specifically\nintended for interacting with collections of Elasticsearch models:\n\n#### Result Meta data\n\nElasticsearch provides a few additional fields in addition to the hits of a query, like the total result amount, or the\nquery execution time. The Elasticsearch\ncollection provides getters for these properties:\n\n```php\nuse App\\Models\\Post;\n\n$posts = Post::all();\n$total = $posts-\u003egetTotal();\n$maxScore = $posts-\u003egetMaxScore();\n$duration = $posts-\u003egetDuration();\n$isTimedOut = $posts-\u003eisTimedOut();\n$scrollId = $posts-\u003egetScrollId();\n$shards = $posts-\u003egetShards();\n```\n\n#### Iterating\n\nSince all of Laravel's collections implement PHP's `iterable` interfaces, you may loop over collections as if they were\nan array:\n\n```php\nforeach ($title as $title) {\n    echo $post-\u003etitle;\n}\n```\n\n### Chunking Results\n\nElasticsearch indices can grow quite huge. Your application may run out of memory if you would attempt to load tens of\nthousands of Elasticsearch documents via\nthe `all` or `get` methods without an upper bound. Therefore, the default amount of documents fetched is set to `10`. To\nchange this, use the `take` method:\n\n```php\nuse App\\Models\\Post;\n\n$posts = Post::take(500)-\u003eget();\n```\n\n### Retrieving individual Models\n\nIn addition to retrieving all the documents matching a given query, you may also retrieve single documents using the\n`find`, `first`, or `firstWhere` methods.\nInstead of returning a collection of models, these methods return a single model instance:\n\n```php\nuse App\\Models\\Post;\n\n// Retrieve a model by its ID...\n$posts = Post::find('AVp_tCaAoV7YQD3Esfmp');\n\n// Retrieve the first model matching the query constraints...\n$post = Post::where('published', 1)-\u003efirst();\n\n// Alternative to retrieving the first model matching the query constraints...\n$post = Post::firstWhere('published', 1);```\n```\n\nSometimes you may wish to retrieve the first result of a query or perform some other action if no results are found. The\n`firstOr` method will return the first\nresult matching the query or, if no results are found, execute the given closure. The value returned by the closure will\nbe considered the result of the\n`firstOr` method:\n\n```php\nuse App\\Models\\Post;\n\n$model = Post::where('tags', '\u003e', 3)-\u003efirstOr(function () {\n    // ...\n});\n```\n\n### Not Found Exceptions\n\nSometimes you may wish to throw an exception if a model is not found. This is particularly useful in routes or\ncontrollers. The `findOrFail` and `firstOrFail`\nmethods will retrieve the first result of the query; however, if no result is found, a\n[`Matchory\\Elasticsearch\\Exceptions\\DocumentNotFoundException`](./src/Exceptions/DocumentNotFoundException.php) will be\nthrown:\n\n```php\n$post = Post::findOrFail('AVp_tCaAoV7YQD3Esfmp');\n\n$post = Post::where('published', true)-\u003efirstOrFail();\n```\n\nIf the `DocumentNotFoundException` is not caught, a 404 HTTP response is automatically sent back to the client:\n\n```php\nuse App\\Models\\Post;\n\nRoute::get('/api/posts/{id}', function ($id) {\n    return Post::findOrFail($id);\n});\n```\n\n### Inserting and Updating Models\n\n#### Inserts\n\nTo insert a new document into the index, you should instantiate a new model instance and set attributes on the model.\nThen, call the `save` method on the model\ninstance:\n\n```php\nnamespace App\\Http\\Controllers;\n\nuse App\\Models\\Post;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Response;\nuse App\\Http\\Controllers\\Controller;\n\nclass PostController extends Controller\n{\n    /**\n     * Create a new post instance.\n     *\n     * @param  Request  $request\n     * @return Response\n     */\n    public function store(Request $request): Response\n    {\n        // Validate the request...\n\n        $post = new Post;\n        $post-\u003etitle = $request-\u003etitle;\n\n        $post-\u003esave();\n    }\n}\n```\n\nIn this example, we assign the `name` field from the incoming HTTP request to the `name` attribute of the\n`App\\Models\\Post` model instance. When we call the\n`save` method, a document will be inserted into the index.\n\nAlternatively, you may use the `create` method to \"save\" a new model using a single PHP statement. The inserted model\ninstance will be returned to you by the\n`create` method:\n\n```php\nuse App\\Models\\Post;\n\n$post = Post::create([\n'title' =\u003e 'Searching efficiently',\n]);\n```\n\nHowever, before using the create method, you will need to specify either a `fillable` or `guarded` property on your\nmodel class. These properties are required\nbecause all Elasticsearch models are protected against mass assignment vulnerabilities by default. To learn more about\nmass assignment, please consult the\n[mass assignment documentation](https://laravel.com/docs/8.x/eloquent#mass-assignment).\n\n#### Updates\n\nThe `save` method may also be used to update models that already exist in the index. To update a model, you should\nretrieve it and set any attributes you wish\nto update. Then, you should call the model's `save` method.\n\nThe `save()` method may also be used to update models that already exist. To update a model, you should retrieve it, set\nany attributes you wish to update, and\nthen call the save method.\n\n```php\nuse App\\Models\\Post;\n\n$post = Post::find('AVp_tCaAoV7YQD3Esfmp');\n\n$post-\u003etitle = 'Modified Post Title';\n\n$post-\u003esave();\n```\n\n#### Examining Attribute Changes\n\nElasticsearch provides the `isDirty`, `isClean`, and `wasChanged` methods to examine the internal state of your model\nand determine how its attributes have\nchanged from when the model was originally retrieved.\n\nThe `isDirty` method determines if any of the model's attributes have been changed since the model was retrieved. You\nmay pass a specific attribute name to the\n`isDirty` method to determine if a particular attribute is _dirty_. The `isClean` will determine if an attribute has\nremained unchanged since the model was\nretrieved. This method also accepts an optional attribute argument:\n\n```php\nuse App\\Models\\Author;\n\n$author = Author::create([\n'first_name' =\u003e 'Moritz',\n'last_name' =\u003e 'Friedrich',\n'title' =\u003e 'Developer',\n]);\n\n$author-\u003etitle = 'Painter';\n\n$author-\u003eisDirty(); // true\n$author-\u003eisDirty('title'); // true\n$author-\u003eisDirty('first_name'); // false\n\n$author-\u003eisClean(); // false\n$author-\u003eisClean('title'); // false\n$author-\u003eisClean('first_name'); // true\n\n$author-\u003esave();\n\n$author-\u003eisDirty(); // false\n$author-\u003eisClean(); // true\n```\n\nThe `wasChanged` method determines if any attributes were changed when the model was last saved within the current\nrequest cycle. If needed, you may pass an\nattribute name to see if a particular attribute was changed:\n\n```php\nuse App\\Models\\Author;\n\n$author = Author::create([\n'first_name' =\u003e 'Taylor',\n'last_name' =\u003e 'Otwell',\n'title' =\u003e 'Developer',\n]);\n\n$author-\u003etitle = 'Painter';\n\n$author-\u003esave();\n\n$author-\u003ewasChanged(); // true\n$author-\u003ewasChanged('title'); // true\n$author-\u003ewasChanged('first_name'); // false\n```\n\nThe `getOriginal` method returns an array containing the original attributes of the model regardless of any changes to\nthe model since it was retrieved. If\nneeded, you may pass a specific attribute name to get the original value of a particular attribute:\n\n```php\nuse App\\Models\\Author;\n\n$author = Author::find(1);\n\n$author-\u003ename; // John\n$author-\u003eemail; // john@example.com\n\n$author-\u003ename = \"Jack\";\n$author-\u003ename; // Jack\n\n$author-\u003egetOriginal('name'); // John\n$author-\u003egetOriginal(); // Array of original attributes...\n```\n\n#### Mass Assignment\n\nYou may use the `create` method to \"save\" a new model using a single PHP statement. The inserted model instance will be\nreturned to you by the method:\n\n```php\nuse App\\Models\\Post;\n\n$post = Post::create([\n    'title' =\u003e 'Searching effectively',\n]);\n```\n\nHowever, before using the `create` method, you will need to specify either a `fillable` or `guarded` property on your\nmodel class. These properties are required\nbecause all Elasticsearch models are protected against mass assignment vulnerabilities by default.\n\nA mass assignment vulnerability occurs when a user passes an unexpected HTTP request field and that field changes a\nfield in your index that you did not expect.\n\nSo, to get started, you should define which model attributes you want to make mass assignable. You may do this using the\n`fillable` property on the model. For\nexample, let's make the `title` attribute of our `Post` model mass assignable:\n\n```php\nnamespace App\\Models;\n\nuse Matchory\\Elasticsearch\\Model;\n\nclass Post extends Model\n{\n    /**\n     * The attributes that are mass assignable.\n     *\n     * @var array\n     */\n    protected $fillable = ['title'];\n}\n```\n\nOnce you have specified which attributes are mass assignable, you may use the `create` method to insert a new document\nin the index. The `create` method returns\nthe newly created model instance:\n\n```php\n$post = Post::create(['title' =\u003e 'Searching effectively']);\n```\n\nIf you already have a model instance, you may use the `fill` method to populate it with an array of attributes:\n\n```php\n$post-\u003efill(['title' =\u003e 'Searching more effectively']);\n```\n\n#### Allowing Mass Assignment\n\nIf you would like to make all of your attributes mass assignable, you may define your model's `guarded` property as an\nempty array. If you choose to un-guard\nyour model, you should take special care to always hand-craft the arrays passed to Elasticsearch's `fill`, `create`, and\n`update` methods:\n\n```php\n/**\n * The attributes that aren't mass assignable.\n *\n * @var array\n */\nprotected $guarded = [];\n```\n\n#### Upserts\n\nThere is currently no convenience wrapper for upserting documents (inserting or updating depending on whether models\nexist). If you're interested in such a\ncapability, please open an issue.\n\n##### Deleting Models\n\nTo delete a model, call the `delete` method on a model instance:\n\n```php\nuse App\\Models\\Post;\n\n$post = Post::find('AVp_tCaAoV7YQD3Esfmp');\n\n$post-\u003edelete();\n```\n\n#### Deleting An Existing Model By Its ID\n\nIn the example above, we are retrieving the model from the index before calling the `delete` method. However, if you\nknow the ID of the model, you may delete\nthe model without explicitly retrieving it by calling the `destroy` method. In addition to accepting the single ID, the\n`destroy` method will accept multiple\nIDs, an array of IDs, or a collection of IDs:\n\n```php\nuse App\\Models\\Post;\n\nPost::destroy(1);\n\nPost::destroy(1, 2, 3);\n\nPost::destroy([1, 2, 3]);\n\nPost::destroy(collect([1, 2, 3]));\n```\n\n\u003e **Important:**  \n\u003e The `destroy` method loads each model individually and calls the `delete` method so that the `deleting` and `deleted`\n\u003e events are properly dispatched for\n\u003e each model.\n\n### Query Scopes\n\nQuery scopes are implemented exactly the way as they are in Eloquent.\n\n#### Global Scopes\n\nGlobal scopes allow you to add constraints to all queries for a given model. Writing your own global scopes can provide\na convenient, easy way to make sure\nevery query for a given model receives certain constraints.\n\n##### Writing Global Scopes\n\nWriting a global scope is simple. First, define a class that implements the\n[`Matchory\\Elasticsearch\\Interfaces\\ScopeInterface`](./src/Interfaces/ScopeInterface.php) interface. Laravel does not\nhave a conventional location that you\nshould place scope classes, so you are free to place this class in any directory that you wish.\n\nThe `ScopeInterface` requires you to implement one method: `apply`. The `apply` method may add constraints or other\ntypes of clauses to the query as needed:\n\n```php\nnamespace App\\Scopes;\n\nuse Matchory\\Elasticsearch\\Query;\nuse Matchory\\Elasticsearch\\Model;\nuse Matchory\\Elasticsearch\\Interfaces\\ScopeInterface;\n\nclass AncientScope implements ScopeInterface\n{\n    /**\n     * Apply the scope to a given Elasticsearch query builder.\n     *\n     * @param  \\Matchory\\Elasticsearch\\Query  $query\n     * @param  \\Matchory\\Elasticsearch\\Model  $model\n     * @return void\n     */\n    public function apply(Query $query, Model $model)\n    {\n        $query-\u003ewhere('created_at', '\u003c', now()-\u003esubYears(2000));\n    }\n}\n```\n\n##### Applying Global Scopes\n\nTo assign a global scope to a model, you should override the model's booted method and invoke the model's\n`addGlobalScope` method. The `addGlobalScope` method\naccepts an instance of your scope as its only argument:\n\n```php\nnamespace App\\Models;\n\nuse App\\Scopes\\AncientScope;\nuse Matchory\\Elasticsearch\\Model;\n\nclass Post extends Model\n{\n    /**\n     * The \"booted\" method of the model.\n     *\n     * @return void\n     */\n    protected static function booted()\n    {\n        static::addGlobalScope(new AncientScope);\n    }\n}\n```\n\n##### Anonymous Global Scopes\n\nElasticsearch also allows you to define global scopes using closures, which is particularly useful for simple scopes\nthat do not warrant a separate class of\ntheir own. When defining a global scope using a closure, you should provide a scope name of your own choosing as the\nfirst argument to the\n`addGlobalScope` method:\n\n```php\nnamespace App\\Models;\n\nuse Matchory\\Elasticsearch\\Query;\nuse Matchory\\Elasticsearch\\Model;\n\nclass Post extends Model\n{\n    /**\n     * The \"booted\" method of the model.\n     *\n     * @return void\n     */\n    protected static function booted(): void\n    {\n        static::addGlobalScope('ancient', function (Query $query) {\n            $query-\u003ewhere('created_at', '\u003c', now()-\u003esubYears(2000));\n        });\n    }\n}\n```\n\n##### Removing Global Scopes\n\nIf you would like to remove a global scope for a given query, you may use the `withoutGlobalScope` method. This method\naccepts the class name of the global\nscope as its only argument:\n\n```php\nPost::withoutGlobalScope(AncientScope::class)-\u003eget();\n```\n\nOr, if you defined the global scope using a closure, you should pass the string name that you assigned to the global\nscope:\n\n```php\nPost::withoutGlobalScope('ancient')-\u003eget();\n```\n\nIf you would like to remove several or even all of the query's global scopes, you may use the `withoutGlobalScopes`\nmethod:\n\n```php\n// Remove all of the global scopes...\nPost::withoutGlobalScopes()-\u003eget();\n```\n\n```php\n// Remove some of the global scopes...\nPost::withoutGlobalScopes([\n    FirstScope::class,\n    SecondScope::class\n])-\u003eget();\n```\n\n#### Local Scopes\n\nLocal scopes allow you to define common sets of query constraints that you may easily re-use throughout your\napplication. For example, you may need to\nfrequently retrieve all posts that are considered \"popular\".\n\n##### Writing local scopes\n\nTo define a scope, prefix an Elasticsearch model method with scope. Scopes should always return a query builder\ninstance:\n\n```php\nnamespace App\\Models;\n\nuse Matchory\\Elasticsearch\\Model;\n\nclass Post extends Model\n{\n    /**\n     * Scope a query to only include popular posts.\n     *\n     * @param  \\Matchory\\Elasticsearch\\Query  $query\n     * @return \\Matchory\\Elasticsearch\\Query\n     */\n    public function scopePopular(Query $query): Query\n    {\n        return $query-\u003ewhere('votes', '\u003e', 100);\n    }\n\n    /**\n     * Scope a query to only include published posts.\n     *\n     * @param  \\Matchory\\Elasticsearch\\Query  $query\n     * @return \\Matchory\\Elasticsearch\\Query\n     */\n    public function scopePublished(Query $query): Query\n    {\n        return $query-\u003ewhere('published', 1);\n    }\n}\n```\n\n##### Utilizing local scopes\n\nOnce the scope has been defined, you may call the scope methods when querying the model. However, you should not include\nthe scope prefix when calling the\nmethod. You can even chain calls to various scopes:\n\n```php\nuse App\\Models\\Post;\n\n$posts = Post::popular()-\u003epublished()-\u003eorderBy('created_at')-\u003eget();\n```\n\n#### Dynamic Scopes\n\nSometimes you may wish to define a scope that accepts parameters. To get started, just add your additional parameters to\nyour scope method's signature. Scope\nparameters should be defined after the `$query` parameter:\n\n```php\nnamespace App\\Models;\n\nuse Matchory\\Elasticsearch\\Model;\n\nclass Post extends Model\n{\n    /**\n     * Scope a query to only include posts of a given type.\n     *\n     * @param  \\Matchory\\Elasticsearch\\Query  $query\n     * @param  mixed  $type\n     * @return \\Matchory\\Elasticsearch\\Query\n     */\n    public function scopeOfType(Query $query, $type): Query\n    {\n        return $query-\u003ewhere('type', $type);\n    }\n}\n```\n\nOnce the expected arguments have been added to your scope method's signature, you may pass the arguments when calling\nthe scope:\n\n```\n$posts = Post::ofType('news')-\u003eget();\n```\n\n### Comparing Models\n\nSometimes you may need to determine if two models are the \"same\". The is method may be used to quickly verify two models\nhave the same ID, index, type, and\nconnection:\n\n```php\nif ($post-\u003eis($anotherPost)) {\n    //\n}\n```\n\n### Events\n\nElasticsearch models dispatch several events, allowing you to hook into the following moments in a model's lifecycle:\n`retrieved`, `creating`, `created`,\n`updating`, `updated`, `saving`, `saved`, `deleting`, `deleted`, `restoring`, `restored`, and `replicating`.\n\nThe `retrieved` event will dispatch when an existing model is retrieved from the index. When a new model is saved for\nthe first time, the `creating` and\n`created` events will dispatch. The `updating` / `updated` events will dispatch when an existing model is modified, and\nthe `save` method is called. The\n`saving` / `saved` events will dispatch when a model is created or updated - even if the model's attributes have not\nbeen changed.\n\nTo start listening to model events, define a `dispatchesEvents` property on your Elasticsearch model. This property maps\nvarious points of the Elasticsearch\nmodel's lifecycle to your own [event classes](https://laravel.com/docs/8.x/events). Each model event class should expect\nto receive an instance of the affected\nmodel via its constructor:\n\n```php\nnamespace App\\Models;\n\nuse Matchory\\Elasticsearch\\Model;\nuse App\\Events\\UserDeleted;\nuse App\\Events\\UserSaved;\n\nclass Post extends Model\n{\n    /**\n     * The event map for the model.\n     *\n     * @var array\n     */\n    protected $dispatchesEvents = [\n        'saved' =\u003e PostSaved::class,\n        'deleted' =\u003e PostDeleted::class,\n    ];\n}\n```\n\nAfter defining and mapping your events, you may\nuse [event listeners](https://laravel.com/docs/8.x/events#defining-listeners) to handle the events.\n\n#### Using Closures\n\nInstead of using custom event classes, you may register closures that execute when various model events are dispatched.\nTypically, you should register these\nclosures in the `booted` method of your model:\n\n```php\nnamespace App\\Models;\n\nuse Matchory\\Elasticsearch\\Model;\n\nclass Post extends Model\n{\n    /**\n     * The \"booted\" method of the model.\n     *\n     * @return void\n     */\n    protected static function booted(): void\n    {\n        static::created(function ($post) {\n            //\n        });\n    }\n}\n```\n\nIf needed, you may\nutilize [queueable anonymous event listeners](https://laravel.com/docs/8.x/events#queuable-anonymous-event-listeners)\nwhen registering model\nevents. This will instruct Laravel to execute the model event listener in the background using your\napplication's [queue](https://laravel.com/docs/8.x/queues):\n\n```php\nuse function Illuminate\\Events\\queueable;\n\nstatic::created(queueable(function ($post): void {\n    //\n}));\n```\n\n##### Accessors \u0026 Mutators\n\n###### Defining An Accessor\n\nTo define an `accessor`, create a `getFooAttribute` method on your model where `Foo` is the \"studly\" cased name of the\nfield you wish to access. In this\nexample, we'll define an accessor for the `title` attribute. The accessor will automatically be called by model when\nattempting to retrieve the value of the\n`title` attribute:\n\n```php\n\nnamespace App;\n\nuse Matchory\\Elasticsearch\\Model;\n\nclass post extends Model\n{\n    /**\n     * Get the post title.\n     *\n     * @param  string  $value\n     * @return string\n     */\n    public function getTitleAttribute(string $value): string\n    {\n        return ucfirst($value);\n    }\n}\n```\n\nAs you can see, the original value of the field is passed to the accessor, allowing you to manipulate and return the\nvalue. To access the value of the accessor,\nyou may simply access the `title` attribute on a model instance:\n\n```php\n$post = App\\Post::find(1);\n\n$title = $post-\u003etitle;\n```\n\nOccasionally, you may need to add array attributes that do not have a corresponding field in your index. To do so,\nsimply define an accessor for the value:\n\n```php\npublic function getIsPublishedAttribute(): bool\n{\n    return $this-\u003eattributes['status'] === 1;\n}\n```\n\nOnce you have created the accessor, just add the value to the `appends` property on the model:\n\n```php\nprotected $appends = ['is_published'];\n```\n\nOnce the attribute has been added to the appends list, it will be included in model's array.\n\n###### Defining A Mutator\n\nTo define a mutator, define a `setFooAttribute` method on your model where `Foo` is the \"studly\" cased name of the field\nyou wish to access. So, again, let's\ndefine a mutator for the `title` attribute. This mutator will be automatically called when we attempt to set the value\nof the `title`attribute on the model:\n\n```php\nnamespace App;\n\nuse Matchory\\Elasticsearch\\Model;\n\nclass post extends Model\n{\n    /**\n     * Set the post title.\n     *\n     * @param  string  $value\n     * @return string\n     */\n    public function setTitleAttribute(string $value): string\n    {\n        return strtolower($value);\n    }\n}\n```\n\nThe mutator will receive the value that is being set on the attribute, allowing you to manipulate the value and set the\nmanipulated value on the model's\ninternal `$attributes` property. So, for example, if we attempt to set the title attribute to `Awesome post to read`:\n\n```php\n$post = App\\Post::find(1);\n\n$post-\u003etitle = 'Awesome post to read';\n```\n\nIn this example, the setTitleAttribute function will be called with the value `Awesome post to read`. The mutator will\nthen apply the strtolower function to the\nname and set its resulting value in the internal $attributes array.\n\n#### Muting Events\n\nYou may occasionally need to temporarily \"mute\" all events fired by a model. You may achieve this using the\n`withoutEvents` method. The `withoutEvents` method\naccepts a closure as its only argument. Any code executed within this closure will not dispatch model events. For\nexample, the following example will fetch and\ndelete an `App\\Models\\Post` instance without dispatching any model events. Any value returned by the closure will be\nreturned by the `withoutEvents` method:\n\n```php\nuse App\\Models\\Post;\n\n$post = Post::withoutEvents(function () use () {\nPost::findOrFail(1)-\u003edelete();\n\n    return Post::find(2);\n});\n```\n\n#### Saving A Single Model Without Events\n\nSometimes you may wish to \"save\" a given model without dispatching any events. You may accomplish this using the\n`saveQuietly` method:\n\n```php\n$post = Post::findOrFail(1);\n\n$post-\u003etitle = 'Other search strategies';\n\n$post-\u003esaveQuietly();\n```\n\n### Replicating Models\n\nYou may create an unsaved copy of an existing model instance using the replicate method. This method is particularly\nuseful when you have model instances that\nshare many of the same attributes:\n\n```php\nuse App\\Models\\Address;\n\n$shipping = Address::create([\n    'type' =\u003e 'shipping',\n    'line_1' =\u003e '123 Example Street',\n    'city' =\u003e 'Victorville',\n    'state' =\u003e 'CA',\n    'postcode' =\u003e '90001',\n]);\n\n$billing = $shipping-\u003ereplicate()-\u003efill([\n    'type' =\u003e 'billing'\n]);\n\n$billing-\u003esave();\n```\n\n### Mutators and Casting\n\nAccessors, mutators, and attribute casting allow you to transform Elasticsearch attribute values when you retrieve or\nset them on model instances. For example,\nyou may want to use the [Laravel encrypter](https://laravel.com/docs/8.x/encryption) to encrypt a value while it is\nstored in the index, and then automatically\ndecrypt the attribute when you access it on an Elasticsearch model. Or, you may want to convert a JSON string that is\nstored in your index to an array when it\nis accessed via your Elasticsearch model.\n\n#### Accessors \u0026 Mutators\n\n##### Defining An Accessor\n\nAn accessor transforms an Elasticsearch attribute value when it is accessed. To define an accessor, create a\n`get{Attribute}Attribute` method on your model\nwhere `{Attribute}` is the \"studly\" cased name of the field you wish to access.\n\nIn this example, we'll define an accessor for the `first_name` attribute. The accessor will automatically be called by\nElasticsearch when attempting to retrieve\nthe value of the `first_name` attribute:\n\n```php\nnamespace App\\Models;\n\nuse Matchory\\Elasticsearch\\Model;\n\nclass User extends Model\n{\n    /**\n     * Get the user's first name.\n     *\n     * @param  string  $value\n     * @return string\n     */\n    public function getFirstNameAttribute(string $value): string\n    {\n        return ucfirst($value);\n    }\n}\n```\n\nAs you can see, the original value of the field is passed to the accessor, allowing you to manipulate and return the\nvalue. To access the value of the accessor,\nyou may simply access the `first_name` attribute on a model instance:\n\n```php\nuse App\\Models\\User;\n\n$user = User::find(1);\n\n$firstName = $user-\u003efirst_name;\n```\n\nYou are not limited to interacting with a single attribute within your accessor. You may also use accessors to return\nnew, computed values from existing\nattributes:\n\n```php\n/**\n * Get the user's full name.\n *\n * @return string\n */\npublic function getFullNameAttribute(): string\n{\n    return \"{$this-\u003efirst_name} {$this-\u003elast_name}\";\n}\n```\n\n##### Defining A Mutator\n\nA mutator transforms an Elasticsearch attribute value when it is set. To define a mutator, define a\n`set{Attribute}Attribute` method on your model where\n`{Attribute}` is the \"studly\" cased name of the field you wish to access.\n\nLet's define a mutator for the `first_name` attribute. This mutator will be automatically called when we attempt to set\nthe value of the `first_name` attribute\non the model:\n\n```php\nnamespace App\\Models;\n\nuse Matchory\\Elasticsearch\\Model;\n\nclass User extends Model\n{\n    /**\n     * Set the user's first name.\n     *\n     * @param  string  $value\n     * @return void\n     */\n    public function setFirstNameAttribute(string $value): void\n    {\n        $this-\u003eattributes['first_name'] = strtolower($value);\n    }\n}\n```\n\nThe mutator will receive the value that is being set on the attribute, allowing you to manipulate the value and set the\nmanipulated value on the Elasticsearch\nmodel's internal `$attributes` property. To use our mutator, we only need to set the `first_name` attribute on an\nElasticsearch model:\n\n```php\nuse App\\Models\\User;\n\n$user = User::find(1);\n\n$user-\u003efirst_name = 'Sally';\n```\n\nIn this example, the `setFirstNameAttribute` function will be called with the value `Sally`. The mutator will then apply\nthe `strtolower` function to the name\nand set its resulting value in the internal `$attributes` array.\n\n#### Attribute Casting\n\nAttribute casting provides functionality similar to accessors and mutators without requiring you to define any\nadditional methods on your model. Instead, your\nmodel's `$casts` property provides a convenient method of converting attributes to common data types.\n\nThe `$casts` property should be an array where the key is the name of the attribute being cast, and the value is the\ntype you wish to cast the field to. The\nsupported cast types are:\n\n- `array`\n- `boolean`\n- `collection`\n- `date`\n- `datetime`\n- `decimal:\u003cdigits\u003e`\n- `double`\n- `encrypted`\n- `encrypted:array`\n- `encrypted:collection`\n- `encrypted:object`\n- `float`\n- `integer`\n- `object`\n- `real`\n- `string`\n- `timestamp`\n\nTo demonstrate attribute casting, let's cast the `is_admin` attribute, which is stored in our index as an integer (`0`\nor `1`) to a boolean value:\n\n```php\nnamespace App\\Models;\n\nuse Matchory\\Elasticsearch\\Model;\n\nclass User extends Model\n{\n    /**\n     * The attributes that should be cast.\n     *\n     * @var array\n     */\n    protected $casts = [\n        'is_admin' =\u003e 'boolean',\n    ];\n}\n```\n\nAfter defining the cast, the `is_admin` attribute will always be cast to a boolean when you access it, even if the\nunderlying value is stored in the index as an\ninteger:\n\n```php\n$user = App\\Models\\User::find(1);\n\nif ($user-\u003eis_admin) {\n    //\n}\n```\n\n\u003e **Note:** Attributes that are `null` will not be cast.\n\n##### Date Casting\n\nYou may cast date attributes by defining them within your model's `$cast` property array. Typically, dates should be\ncast using the `datetime` cast.\n\nWhen defining a `date` or `datetime` cast, you may also specify the date's format. This format will be used when the\n[model is serialized to an array or JSON](https://laravel.com/docs/8.x/eloquent-serialization):\n\n```php\n/**\n * The attributes that should be cast.\n *\n * @var array\n */\nprotected $casts = [\n    'created_at' =\u003e 'datetime:Y-m-d',\n];\n```\n\nWhen a field is cast as a date, you may set its value to a UNIX timestamp, date string (`Y-m-d`), date-time string, or a\n`DateTime` / `Carbon` instance. The\ndate's value will be correctly converted and stored in your index:\n\nYou may customize the default serialization format for all of your model's dates by defining a `serializeDate` method on\nyour model. This method does not affect\nhow your dates are formatted for storage in the index:\n\n```php\n/**\n * Prepare a date for array / JSON serialization.\n *\n * @param  \\DateTimeInterface  $date\n * @return string\n */\nprotected function serializeDate(DateTimeInterface $date)\n{\n    return $date-\u003eformat('Y-m-d');\n}\n```\n\nTo specify the format that should be used when actually storing a model's dates within your index, you should define a\n`$dateFormat` property on your model:\n\n```php\n/**\n * The storage format of the model's date fields.\n *\n * @var string\n */\nprotected $dateFormat = 'U';\n```\n\n#### Custom Casts\n\nLaravel has a variety of built-in, helpful cast types; however, you may occasionally need to define your own cast types.\nYou may accomplish this by defining a\nclass that implements the `CastsAttributes` interface.\n\nClasses that implement this interface must define a `get` and `set` method. The `get` method is responsible for\ntransforming a raw value from the index into a\ncast value, while the `set` method should transform a cast value into a raw value that can be stored in the index. As an\nexample, we will re-implement the\nbuilt-in `json` cast type as a custom cast type:\n\n\u003e **Note:** Due to type incompatibility, you will need to use different casts for Eloquent and Elasticsearch models, or\n\u003e omit the parameter type.\n\n```php\nnamespace App\\Casts;\n\nuse Illuminate\\Contracts\\Database\\Eloquent\\CastsAttributes;\n\nclass Json implements CastsAttributes\n{\n    /**\n     * Cast the given value.\n     *\n     * @param  \\Illuminate\\Database\\Eloquent\\Model|\\Matchory\\Elasticsearch\\Model  $model\n     * @param  string  $key\n     * @param  mixed  $value\n     * @param  array  $attributes\n     * @return array\n     */\n    public function get($model, $key, $value, $attributes)\n    {\n        return json_decode($value, true);\n    }\n\n    /**\n     * Prepare the given value for storage.\n     *\n     * @param  \\Illuminate\\Database\\Eloquent\\Model|\\Matchory\\Elasticsearch\\Model  $model\n     * @param  string  $key\n     * @param  array  $value\n     * @param  array  $attributes\n     * @return string\n     */\n    public function set($model, $key, $value, $attributes)\n    {\n        return json_encode($value);\n    }\n}\n```\n\nOnce you have defined a custom cast type, you may attach it to a model attribute using its class name:\n\n```php\n    namespace App\\Models;\n\n    use App\\Casts\\Json;\n    use Matchory\\Elasticsearch\\Model;\n\n    class User extends Model\n    {\n        /**\n         * The attributes that should be cast.\n         *\n         * @var array\n         */\n        protected $casts = [\n            'options' =\u003e Json::class,\n        ];\n    }\n```\n\n##### Value Object Casting\n\nYou are not limited to casting values to primitive types. You may also cast values to objects. Defining custom casts\nthat cast values to objects is very similar\nto casting to primitive types; however, the `set` method should return an array of key / value pairs that will be used\nto set raw, storable values on the model.\n\nAs an example, we will define a custom cast class that casts multiple model values into a single `Address` value object.\nWe will assume the `Address` value has\ntwo public properties: `lineOne` and `lineTwo`:\n\n```php\nnamespace App\\Casts;\n\nuse App\\Models\\Address as AddressModel;\nuse Illuminate\\Contracts\\Database\\Eloquent\\CastsAttributes;\nuse InvalidArgumentException;\n\nclass Address implements CastsAttributes\n{\n    /**\n     * Cast the given value.\n     *\n     * @param  \\Illuminate\\Database\\Eloquent\\Model|\\Matchory\\Elasticsearch\\Model  $model\n     * @param  string  $key\n     * @param  mixed  $value\n     * @param  array  $attributes\n     * @return \\App\\Models\\Address\n     */\n    public function get($model, $key, $value, $attributes)\n    {\n        return new AddressModel(\n            $attributes['address_line_one'],\n            $attributes['address_line_two']\n        );\n    }\n\n    /**\n     * Prepare the given value for storage.\n     *\n     * @param  \\Illuminate\\Database\\Eloquent\\Model|\\Matchory\\Elasticsearch\\Model  $model\n     * @param  string  $key\n     * @param  \\App\\Models\\Address  $value\n     * @param  array  $attributes\n     * @return array\n     */\n    public function set($model, $key, $value, $attributes)\n    {\n        if (! $value instanceof AddressModel) {\n            throw new InvalidArgumentException('The given value is not an Address instance.');\n        }\n\n        return [\n            'address_line_one' =\u003e $value-\u003elineOne,\n            'address_line_two' =\u003e $value-\u003elineTwo,\n        ];\n    }\n}\n```\n\nWhen casting to value objects, any changes made to the value object will automatically be synced back to the model\nbefore the model is saved:\n\n```php\nuse App\\Models\\User;\n\n$user = User::find(1);\n\n$user-\u003eaddress-\u003elineOne = 'Updated Address Value';\n\n$user-\u003esave();\n```\n\n\u003e **Tip:** If you plan to serialize your Elasticsearch models containing value objects to JSON or arrays, you should\n\u003e implement the\n\u003e `Illuminate\\Contracts\\Support\\Arrayable` and `JsonSerializable` interfaces on the value object.\n\n##### Array / JSON Serialization\n\nWhen an Elasticsearch model is converted to an array or JSON using the `toArray` and `toJson` methods, your custom cast\nvalue objects will typically be\nserialized as well as long as they implement the `Illuminate\\Contracts\\Support\\Arrayable` and `JsonSerializable`\ninterfaces. However, when using value objects\nprovided by third-party libraries, you may not have the ability to add these interfaces to the object.\n\nTherefore, you may specify that your custom cast class will be responsible for serializing the value object. To do so,\nyour custom class cast should implement\nthe `Illuminate\\Contracts\\Database\\Eloquent\\SerializesCastableAttributes` interface. This interface states that your\nclass should contain a `serialize` method\nwhich should return the serialized form of your value object:\n\n```php\n/**\n * Get the serialized representation of the value.\n *\n * @param  \\Illuminate\\Database\\Eloquent\\Model|\\Matchory\\Elasticsearch\\Model  $model\n * @param  string  $key\n * @param  mixed  $value\n * @param  array  $attributes\n * @return mixed\n */\npublic function serialize($model, string $key, $value, array $attributes)\n{\n    return (string) $value;\n}\n```\n\n##### Inbound Casting\n\nOccasionally, you may need to write a custom cast that only transforms values that are being set on the model and does\nnot perform any operations when\nattributes are being retrieved from the model. A classic example of an inbound only cast is a \"hashing\" cast. Inbound\nonly custom casts should implement the\n`CastsInboundAttributes` interface, which only requires a `set` method to be defined.\n\n```php\nnamespace App\\Casts;\n\nuse Illuminate\\Contracts\\Database\\Eloquent\\CastsInboundAttributes;\n\nclass Hash implements CastsInboundAttributes\n{\n    /**\n     * The hashing algorithm.\n     *\n     * @var string\n     */\n    protected $algorithm;\n\n    /**\n     * Create a new cast class instance.\n     *\n     * @param  string|null  $algorithm\n     * @return void\n     */\n    public function __construct($algorithm = null)\n    {\n        $this-\u003ealgorithm = $algorithm;\n    }\n\n    /**\n     * Prepare the given value for storage.\n     *\n     * @param  \\Illuminate\\Database\\Eloquent\\Model|\\Matchory\\Elasticsearch\\Model  $model\n     * @param  string  $key\n     * @param  array  $value\n     * @param  array  $attributes\n     * @return string\n     */\n    public function set($model, $key, $value, $attributes)\n    {\n        return is_null($this-\u003ealgorithm)\n                    ? bcrypt($value)\n                    : hash($this-\u003ealgorithm, $value);\n    }\n}\n```\n\n##### Cast Parameters\n\nWhen attaching a custom cast to a model, cast parameters may be specified by separating them from the class name using a\n`:` character and comma-delimiting\nmultiple parameters. The parameters will be passed to the constructor of the cast class:\n\n```php\n/**\n * The attributes that should be cast.\n *\n * @var array\n */\nprotected $casts = [\n    'secret' =\u003e Hash::class.':sha256',\n];\n```\n\n##### Castables\n\nYou may want to allow your application's value objects to define their own custom cast classes. Instead of attaching the\ncustom cast class to your model, you\nmay alternatively attach a value object class that implements the `Illuminate\\Contracts\\Database\\Eloquent\\Castable`\ninterface:\n\n```php\nuse App\\Models\\Address;\n\nprotected $casts = [\n    'address' =\u003e Address::class,\n];\n```\n\nObjects that implement the `Castable` interface must define a `castUsing` method that returns the class name of the\ncustom caster class that is responsible for\ncasting to and from the `Castable` class:\n\n```php\nnamespace App\\Models;\n\nuse Illuminate\\Contracts\\Database\\Eloquent\\Castable;\nuse App\\Casts\\Address as AddressCast;\n\nclass Address implements Castable\n{\n    /**\n     * Get the name of the caster class to use when casting from / to this cast target.\n     *\n     * @param  array  $arguments\n     * @return string\n     */\n    public static function castUsing(array $arguments): string\n    {\n        return AddressCast::class;\n    }\n}\n```\n\nWhen using `Castable` classes, you may still provide arguments in the `$casts` definition. The arguments will be passed\nto the `castUsing` method:\n\n```php\nuse App\\Models\\Address;\n\nprotected $casts = [\n    'address' =\u003e Address::class.':argument',\n];\n```\n\n##### Castables \u0026 Anonymous Cast Classes\n\nBy combining \"castables\" with PHP's [anonymous classes](https://www.php.net/manual/en/language.oop5.anonymous.php), you\nmay define a value object and its\ncasting logic as a single castable object. To accomplish this, return an anonymous class from your value object's\n`castUsing` method. The anonymous class should\nimplement the `CastsAttributes` interface:\n\n```php\nnamespace App\\Models;\n\nuse Illuminate\\Contracts\\Database\\Eloquent\\Castable;\nuse Illuminate\\Contracts\\Database\\Eloquent\\CastsAttributes;\n\nclass Address implements Castable\n{\n    // ...\n\n    /**\n     * Get the caster class to use when casting from / to this cast target.\n     *\n     * @param  array  $arguments\n     * @return object|string\n     */\n    public static function castUsing(array $arguments)\n    {\n        return new class implements CastsAttributes\n        {\n            public function get($model, $key, $value, $attributes)\n            {\n                return new Address(\n                    $attributes['address_line_one'],\n                    $attributes['address_line_two']\n                );\n            }\n\n            public function set($model, $key, $value, $attributes)\n            {\n                return [\n                    'address_line_one' =\u003e $value-\u003elineOne,\n                    'address_line_two' =\u003e $value-\u003elineTwo,\n                ];\n            }\n        };\n    }\n}\n```\n\n### Route Model Binding\n\nWhen injecting a model ID to a route or controller action, you will often query the Elasticsearch index to retrieve the\nmodel that corresponds to that ID.\nLaravel route model binding provides a convenient way to automatically inject the model instances directly into your\nroutes. For example, instead of injecting a\nuser's ID, you can inject the entire User model instance that matches the given ID.\n\n#### Implicit Binding\n\nLaravel automatically resolves Elasticsearch models defined in routes or controller actions whose type-hinted variable\nnames match a route segment name. For\nexample:\n\n```php\nuse App\\Models\\Post;\n\nRoute::get('/posts/{post}', function (Post $post) {\n    return $post-\u003econtent;\n});\n```\n\nSince the `$post` variable is type-hinted as the `App\\Models\\Post` Elasticsearch model, and the variable name matches\nthe `{post}` URI segment, Laravel will\nautomatically inject the model instance that has an ID matching the corresponding value from the request URI. If a\nmatching model instance is not found in the\ndatabase, a `404` HTTP response will automatically be generated.\n\nOf course, implicit binding is also possible when using controller methods. Again, note the `{post}` URI segment matches\nthe `$post` variable in the controller\nwhich contains an `App\\Models\\Post` type-hint:\n\n```php\nuse App\\Http\\Controllers\\PostController;\nuse App\\Models\\Post;\n\n// Route definition...\nRoute::get('/posts/{post}', [PostController::class, 'show']);\n\n// Controller method definition...\npublic function show(Post $post): View\n{\n    return view('post.full', ['post' =\u003e $post]);\n}\n```\n\n#### Customizing The Key\n\nSometimes you may wish to resolve Elasticsearch models using a field other than `_id`. To do so, you may specify the\nfield in the route parameter definition:\n\n```php\nuse App\\Models\\Post;\n\nRoute::get('/posts/{post:slug}', fn(Post $post): Post =\u003e $post);\n```\n\nIf you would like model binding to always use an index field other than `_id` when retrieving a given model class, you\nmay override the `getRouteKeyName` method\non the Elasticsearch model:\n\n```php\n/**\n * Get the route key for the model.\n *\n * @return string\n */\npublic function getRouteKeyName(): string\n{\n    return 'slug';\n}\n```\n\n#### Customizing Missing Model Behavior\n\nTypically, a `404` HTTP response will be generated if an implicitly bound model is not found. However, you may customize\nthis behavior by calling the missing\nmethod when defining your route. The missing method accepts a closure that will be invoked if an implicitly bound model\ncan not be found:\n\n```php\nuse App\\Http\\Controllers\\LocationsController;\nuse Illuminate\\Http\\Request;\n\nRoute::get('/locations/{location:slug}', [LocationsController::class, 'show'])\n    -\u003emissing(fn(Request $request) =\u003e Redirect::route('locations.index')\n    -\u003ename('locations.view');\n```\n\n#### Explicit Binding\n\nYou are not required to use Laravel's implicit, convention based model resolution in order to use model binding. You can\nalso explicitly define how route\nparameters correspond to models. To register an explicit binding, use the router's model method to specify the class for\na given parameter. You should define\nyour explicit model bindings at the beginning of the `boot` method of your `RouteServiceProvider` class:\n\n```php\nuse App\\Models\\Post;\nuse Illuminate\\Support\\Facades\\Route;\n\n/**\n * Define your route model bindings, pattern filters, etc.\n *\n * @return void\n */\npublic function boot():void\n{\n    Route::model('post', Post::class);\n\n    // ...\n}\n```  \n\nNext, define a route that contains a `{post}` parameter:\n\n```php\nuse App\\Models\\Post;\n\nRoute::get('/posts/{post}', function (Post $post) {\n    // ...\n});\n```\n\nSince we have bound all `{post}` parameters to the `App\\Models\\Post` model, an instance of that class will be injected\ninto the route. So, for example, a\nrequest to `posts/1` will inject the `Post` instance from the index which has an ID of `1`.\n\nIf a matching model instance is not found in the index, a `404` HTTP response will be automatically generated.\n\n#### Customizing The Resolution Logic\n\nIf you wish to define your own model binding resolution logic, you may use the `Route::bind` method. The closure you\npass to the bind method will receive the\nvalue of the URI segment and should return the instance of the class that should be injected into the route. Again, this\ncustomization should take place in the\n`boot` method of your application's `RouteServiceProvider`:\n\n```php\nuse App\\Models\\Post;\nuse Illuminate\\Support\\Facades\\Route;\n\n/**\n * Define your route model bindings, pattern filters, etc.\n *\n * @return void\n */\npublic function boot(): void\n{\n    Route::bind('post', function (string $value): Post {\n        return Post::where('title', $value)-\u003efirstOrFail();\n    });\n\n    // ...\n}\n```\n\nAlternatively, you may override the `resolveRouteBinding` method on your Elasticsearch model. This method will receive\nthe value of the URI segment and should\nreturn the instance of the class that should be injected into the route:\n\n```php\n/**\n * Retrieve the model for a bound value.\n *\n * @param  mixed  $value\n * @param  string|null  $field\n * @return \\Matchory\\Elasticsearch\\Model|null\n */\npublic function resolveRouteBinding($value, ?string $field = null): ?self\n{\n    return $this-\u003ewhere('name', $value)-\u003efirstOrFail();\n}\n```\n\nIf a route is utilizing implicit binding scoping, the `resolveChildRouteBinding` method will be used to resolve the\nchild binding of the parent model:\n\n```php\n/**\n * Retrieve the child model for a bound value.\n *\n * @param  string  $childType\n * @param  mixed  $value\n * @param  string|null  $field\n * @return \\Matchory\\Elasticsearch\\Model|null\n */\npublic function resolveChildRouteBinding(string $childType, $value, ?string $field): ?self\n{\n    return parent::resolveChildRouteBinding($childType, $value, $field);\n}\n```\n\nUsage as a query builder\n------------------------\nYou can use the `ES` facade to access the query builder directly, from anywhere in your application.\n\n### Creating a new index\n\n```php\nES::create('my_index');\n    \n# or \n    \nES::index('my_index')-\u003ecreate();\n```\n\n### Creating index with custom options (optional)\n\n```php\nuse Matchory\\Elasticsearch\\Facades\\ES;\nuse Matchory\\Elasticsearch\\Index;\n\nES::index('my_index')-\u003ecreate(function(Index $index) {\n    $index-\u003eshards(5)-\u003ereplicas(1)-\u003emapping([\n        'my_type' =\u003e [\n            'properties' =\u003e [\n                'first_name' =\u003e [\n                    'type' =\u003e 'string',\n                ],\n                'age' =\u003e [\n                    'type' =\u003e 'integer'\n                ]\n            ]\n        ]\n    ])\n});\n    \n# or\n    \nES::create('my_index', function(Index $index){\n  \n      $index-\u003eshards(5)-\u003ereplicas(1)-\u003emapping([\n          'my_type' =\u003e [\n              'properties' =\u003e [\n                  'first_name' =\u003e [\n                      'type' =\u003e 'string',\n                  ],\n                  'age' =\u003e [\n                      'type' =\u003e 'integer'\n                  ]\n              ]\n          ]\n      ])\n});\n```\n\n### Dropping an index\n\n```php\nES::drop(\"my_index\");\n    \n# or\n\nES::index(\"my_index\")-\u003edrop();\n```\n\n### Running queries\n\nTo run a query, start by (optionally) selecting the connection and index.\n\n```php\n$documents = ES::connection(\"default\")\n                -\u003eindex(\"my_index\")\n                -\u003etype(\"my_type\")\n                -\u003eget();    # return a collection of results\n```\n\nYou can shorten the above query to:\n\n```php\n$documents = ES::type(\"my_type\")-\u003eget();    # return a collection of results\n```\n\nExplicitly setting connection or index name in the query overrides configuration in `config/es.php`.\n\n### Getting documents by id\n\n```php\nES::type(\"my_type\")-\u003eid(3)-\u003efirst();\n    \n# or\n    \nES::type(\"my_type\")-\u003e_id(3)-\u003efirst();\n```\n\n### Sorting\n\n```php\nES::type(\"my_type\")-\u003eorderBy(\"created_at\", \"desc\")-\u003eget();\n    \n# Sorting with text search score\n    \nES::type(\"my_type\")-\u003eorderBy(\"_score\")-\u003eget();\n```\n\n### Limit and offset\n\n```php\nES::type(\"my_type\")-\u003etake(10)-\u003eskip(5)-\u003eget();\n```\n\n### Select only specific fields\n\n```php\nES::type(\"my_type\")-\u003eselect(\"title\", \"content\")-\u003etake(10)-\u003eskip(5)-\u003eget();\n```\n\n### Where clause\n\n```php\nES::type(\"my_type\")-\u003ewhere(\"status\", \"published\")-\u003eget();\n\n# or\n\nES::type(\"my_type\")-\u003ewhere(\"status\", \"=\", \"published\")-\u003eget();\n```\n\n### Where greater than\n\n```php\nES::type(\"my_type\")-\u003ewhere(\"views\", \"\u003e\", 150)-\u003eget();\n```\n\n### Where greater than or equal\n\n```php\nES::type(\"my_type\")-\u003ewhere(\"views\", \"\u003e=\", 150)-\u003eget();\n```\n\n### Where less than\n\n```php\nES::type(\"my_type\")-\u003ewhere(\"views\", \"\u003c\", 150)-\u003eget();\n```\n\n### Where less than or equal\n\n```php\nES::type(\"my_type\")-\u003ewhere(\"views\", \"\u003c=\", 150)-\u003eget();\n```\n\n### Where like\n\n```php\nES::type(\"my_type\")-\u003ewhere(\"title\", \"like\", \"foo\")-\u003eget();\n```\n\n### Where field exists\n\n```php\nES::type(\"my_type\")-\u003ewhere(\"hobbies\", \"exists\", true)-\u003eget(); \n\n# or \n\nES::type(\"my_type\")-\u003ewhereExists(\"hobbies\", true)-\u003eget();\n```    \n\n### Where in clause\n\n```php\nES::type(\"my_type\")-\u003ewhereIn(\"id\", [100, 150])-\u003eget();\n```\n\n### Where between clause\n\n```php\nES::type(\"my_type\")-\u003ewhereBetween(\"id\", 100, 150)-\u003eget();\n\n# or \n\nES::type(\"my_type\")-\u003ewhereBetween(\"id\", [100, 150])-\u003eget();\n```\n\n### Where not clause\n\n```php\nES::type(\"my_type\")-\u003ewhereNot(\"status\", \"published\")-\u003eget(); \n\n# or\n\nES::type(\"my_type\")-\u003ewhereNot(\"status\", \"=\", \"published\")-\u003eget();\n```\n\n### Where not greater than\n\n```php\nES::type(\"my_type\")-\u003ewhereNot(\"views\", \"\u003e\", 150)-\u003eget();\n```\n\n### Where not greater than or equal\n\n```php\nES::type(\"my_type\")-\u003ewhereNot(\"views\", \"\u003e=\", 150)-\u003eget();\n```\n\n### Where not less than\n\n```php\nES::type(\"my_type\")-\u003ewhereNot(\"views\", \"\u003c\", 150)-\u003eget();\n```\n\n### Where not less than or equal\n\n```php\nES::type(\"my_type\")-\u003ewhereNot(\"views\", \"\u003c=\", 150)-\u003eget();\n```\n\n### Where not like\n\n```php\nES::type(\"my_type\")-\u003ewhereNot(\"title\", \"like\", \"foo\")-\u003eget();\n```\n\n### Where not field exists\n\n```php\nES::type(\"my_type\")-\u003ewhereNot(\"hobbies\", \"exists\", true)-\u003eget(); \n\n# or\n\nES::type(\"my_type\")-\u003ewhereExists(\"hobbies\", true)-\u003eget();\n```\n\n### Where not in clause\n\n```php\nES::type(\"my_type\")-\u003ewhereNotIn(\"id\", [100, 150])-\u003eget();\n```\n\n### Where not between clause\n\n```php\nES::type(\"my_type\")-\u003ewhereNotBetween(\"id\", 100, 150)-\u003eget();\n\n# or\n\nES::type(\"my_type\")-\u003ewhereNotBetween(\"id\", [100, 150])-\u003eget();\n```\n\n### Search by a distance from a geo point\n\n```php\nES::type(\"my_type\")-\u003edistance(\"location\", [\"lat\" =\u003e -33.8688197, \"lon\" =\u003e 151.20929550000005], \"10km\")-\u003eget();\n\n# or\n\nES::type(\"my_type\")-\u003edistance(\"location\", \"-33.8688197,151.20929550000005\", \"10km\")-\u003eget();\n\n# or\n\nES::type(\"my_type\")-\u003edistance(\"location\", [151.20929550000005, -33.8688197], \"10km\")-\u003eget();  \n```\n\n### Search using array queries\n\n```php\nES::type(\"my_type\")-\u003ebody([\n    \"query\" =\u003e [\n         \"bool\" =\u003e [\n             \"must\" =\u003e [\n                 [ \"match\" =\u003e [ \"address\" =\u003e \"mill\" ] ],\n                 [ \"match\" =\u003e [ \"address\" =\u003e \"lane\" ] ]\n             ]\n         ]\n     ]\n])-\u003eget();\n\n# Note that you can mix between query builder and array queries.\n# The query builder will will be merged with the array query.\n\nES::type(\"my_type\")-\u003ebody([\n\t\"_source\" =\u003e [\"content\"]\n\t\n\t\"query\" =\u003e [\n\t     \"bool\" =\u003e [\n\t         \"must\" =\u003e [\n\t             [ \"match\" =\u003e [ \"address\" =\u003e \"mill\" ] ]\n\t         ]\n\t     ]\n\t],\n\t   \n\t\"sort\" =\u003e [\n\t\t\"_score\"\n\t]\n     \n])-\u003eselect(\"name\")-\u003eorderBy(\"created_at\", \"desc\")-\u003etake(10)-\u003eskip(5)-\u003eget();\n\n# The result query will be\n/*\nArray\n(\n    [index] =\u003e my_index\n    [type] =\u003e my_type\n    [body] =\u003e Array\n        (\n            [_source] =\u003e Array\n                (\n                    [0] =\u003e content\n                    [1] =\u003e name\n                )\n            [query] =\u003e Array\n                (\n                    [bool] =\u003e Array\n                        (\n                            [must] =\u003e Array\n                                (\n                                    [0] =\u003e Array\n                                        (\n                                            [match] =\u003e Array\n                                                (\n                                                    [address] =\u003e mill\n                                                )\n                                        )\n                                )\n                        )\n                )\n            [sort] =\u003e Array\n                (\n                    [0] =\u003e _score\n                    [1] =\u003e Array\n                        (\n                            [created_at] =\u003e desc\n                        )\n                )\n        )\n    [from] =\u003e 5\n    [size] =\u003e 10\n    [client] =\u003e Array\n        (\n            [ignore] =\u003e Array\n                (\n                )\n        )\n)\n*/\n```\n\n### Search the entire document\n\n```php\nES::type(\"my_type\")-\u003esearch(\"hello\")-\u003eget();\n    \n# search with Boost = 2\n    \nES::type(\"my_type\")-\u003esearch(\"hello\", 2)-\u003eget();\n\n# search within specific fields with different weights\n\nES::type(\"my_type\")-\u003esearch(\"hello\", function($search){\n\t$search-\u003eboost(2)-\u003efields([\"title\" =\u003e 2, \"content\" =\u003e 1])\n})-\u003eget();\n```\n\n### Search with highlight fields\n\n```php\n$doc = ES::type(\"my_type\")-\u003ehighlight(\"title\")-\u003esearch(\"hello\")-\u003efirst();\n\n# Multiple fields Highlighting is allowed.\n\n$doc = ES::type(\"my_type\")-\u003ehighlight(\"title\", \"content\")-\u003esearch(\"hello\")-\u003efirst();\n\n# Return all highlights as array using $doc-\u003egetHighlights() method.\n\n$doc-\u003egetHighlights();\n\n# Also you can return only highlights of specific field.\n\n$doc-\u003egetHighlights(\"title\");\n```\n\n### Return only first document\n\n```php\nES::type(\"my_type\")-\u003esearch(\"hello\")-\u003efirst();\n```\n\n### Return only count\n\n```php\nES::type(\"my_type\")-\u003esearch(\"hello\")-\u003ecount();\n```\n\n### Scan-and-Scroll queries\n\n```php\n# These queries are suitable for large amount of data. \n# A scrolled search allows you to do an initial search and to keep pulling batches of results\n# from Elasticsearch until there are no more results left.\n# It’s a bit like a cursor in a traditional database\n    \n$documents = ES::type(\"my_type\")-\u003esearch(\"hello\")\n                 -\u003escroll(\"2m\")\n                 -\u003etake(1000)\n                 -\u003eget();\n\n# Response will contain a hashed code `scroll_id` will be used to get the next result by running\n\n$documents = ES::type(\"my_type\")-\u003esearch(\"hello\")\n                 -\u003escroll(\"2m\")\n                 -\u003escrollID(\"DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAFMFlJQOEtTdnJIUklhcU1FX2VqS0EwZncAAAAAAAABSxZSUDhLU3ZySFJJYXFNRV9laktBMGZ3AAAAAAAAAU4WUlA4S1N2ckhSSWFxTUVfZWpLQTBmdwAAAAAAAAFPFlJQOEtTdnJIUklhcU1FX2VqS0EwZncAAAAAAAABTRZSUDhLU3ZySFJJYXFNRV9laktBMGZ3\")\n                 -\u003eget();\n\n# And so on ...\n# Note that you don't need to write the query parameters in every scroll. All you need the `scroll_id` and query scroll time.\n    \n# To clear `scroll_id` \n  \nES::type(\"my_type\")-\u003escrollID(\"DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAFMFlJQOEtTdnJIUklhcU1FX2VqS0EwZncAAAAAAAABSxZSUDhLU3ZySFJJYXFNRV9laktBMGZ3AAAAAAAAAU4WUlA4S1N2ckhSSWFxTUVfZWpLQTBmdwAAAAAAAAFPFlJQOEtTdnJIUklhcU1FX2VqS0EwZncAAAAAAAABTRZSUDhLU3ZySFJJYXFNRV9laktBMGZ3\")\n        -\u003eclear();\n```\n\n### Paginate results with 5 documents per page\n\n```php\n$documents = ES::type(\"my_type\")-\u003esearch(\"hello\")-\u003epaginate(5);\n    \n# Getting pagination links\n    \n$documents-\u003elinks();\n\n# Bootstrap 4 pagination\n\n$documents-\u003elinks(\"bootstrap-4\");\n\n# Simple bootstrap 4 pagination\n\n$documents-\u003elinks(\"simple-bootstrap-4\");\n\n# Simple pagination\n\n$documents-\u003elinks(\"simple-default\");\n```\n\nThese are all pagination methods you may use:\n\n```php\n$documents-\u003ecount()\n$documents-\u003ecurrentPage()\n$documents-\u003efirstItem()\n$documents-\u003ehasMorePages()\n$documents-\u003elastItem()\n$documents-\u003elastPage()\n$documents-\u003enextPageUrl()\n$documents-\u003eperPage()\n$documents-\u003epreviousPageUrl()\n$documents-\u003etotal()\n$documents-\u003eurl($page)\n```\n\n### Getting the query array without execution\n\n```php\nES::type(\"my_type\")-\u003esearch(\"hello\")-\u003ewhere(\"views\", \"\u003e\", 150)-\u003etoArray();\n```\n\n### Getting the original elasticsearch response\n\n```php\nES::type(\"my_type\")-\u003esearch(\"hello\")-\u003ewhere(\"views\", \"\u003e\", 150)-\u003eresponse();\n```\n\n### Ignoring bad HTTP response\n\n```php\nES::type(\"my_type\")-\u003eignore(404, 500)-\u003eid(5)-\u003efirst();\n```\n\n### Query Caching (Laravel)\n\nPackage comes with a built-in caching layer based on laravel cache.\n\n```php\nES::type(\"my_type\")-\u003esearch(\"hello\")-\u003eremember(10)-\u003eget();\n\n# Specify a custom cache key\nES::type(\"my_type\")-\u003esearch(\"hello\")-\u003eremember(10, \"last_documents\")-\u003eget();\n\n# Caching using other available driver\nES::type(\"my_type\")-\u003esearch(\"hello\")-\u003ecacheDriver(\"redis\")-\u003eremember(10, \"last_documents\")-\u003eget();\n\n# Caching with cache key prefix\nES::type(\"my_type\")-\u003esearch(\"hello\")-\u003ecacheDriver(\"redis\")-\u003ecachePrefix(\"docs\")-\u003eremember(10, \"last_documents\")-\u003eget();\n```\n\n### Executing elasticsearch raw queries\n\n```php\nES::raw()-\u003esearch([\n    \"index\" =\u003e \"my_index\",\n    \"type\"  =\u003e \"my_type\",\n    \"body\" =\u003e [\n        \"query\" =\u003e [\n            \"bool\" =\u003e [\n                \"must\" =\u003e [\n                    [ \"match\" =\u003e [ \"address\" =\u003e \"mill\" ] ],\n                    [ \"match\" =\u003e [ \"address\" =\u003e \"lane\" ] ]\n                ]\n            ]\n        ]\n    ]\n]);\n```\n\n### Insert a new document\n\n```php\nES::type(\"my_type\")-\u003eid(3)-\u003einsert([\n    \"title\" =\u003e \"Test document\",\n    \"content\" =\u003e \"Sample content\"\n]);\n     \n# A new document will be inserted with _id = 3.\n# [id is optional] if not specified, a unique hash key will be generated.\n```\n\n### Bulk insert a multiple of documents at once.\n\n```php\n# Main query\nES::index(\"my_index\")-\u003etype(\"my_type\")-\u003ebulk(function ($bulk){\n\n    # Sub queries\n\t$bulk-\u003eindex(\"my_index_1\")-\u003etype(\"my_type_1\")-\u003eid(10)-\u003einsert([\"title\" =\u003e \"Test document 1\",\"content\" =\u003e \"Sample content 1\"]);\n\t$bulk-\u003eindex(\"my_index_2\")-\u003eid(11)-\u003einsert([\"title\" =\u003e \"Test document 2\",\"content\" =\u003e \"Sample content 2\"]);\n\t$bulk-\u003eid(12)-\u003einsert([\"title\" =\u003e \"Test document 3\", \"content\" =\u003e \"Sample content 3\"]);\n\t\n});\n\n# Notes from the above query:\n\n# As index and type names are required for insertion, Index and type names are extendable. This means that: \n\n# If index() is not specified in subquery:\n# -- The builder will get index name from the main query.\n# -- if index is not specified in main query, the builder will get index name from configuration file.\n\n# And\n\n# If type() is not specified in subquery:\n# -- The builder will get type name from the main query.\n# you can use old bulk code style using multidimensional array of [id =\u003e data] pairs\n \nES::type(\"my_type\")-\u003ebulk([\n \n\t10 =\u003e [\n\t\t\"title\" =\u003e \"Test document 1\",\n\t\t\"content\" =\u003e \"Sample content 1\"\n\t],\n\n\t11 =\u003e [\n\t\t\"title\" =\u003e \"Test document 2\",\n\t\t\"content\" =\u003e \"Sample content 2\"\n\t]\n \n]);\n \n# The two given documents will be inserted with its associated ids\n```\n\n### Update an existing document\n\n```php\nES::type(\"my_type\")-\u003eid(3)-\u003eupdate([\n   \"title\" =\u003e \"Test document\",\n   \"content\" =\u003e \"sample content\"\n]);\n    \n# Document has _id = 3 will be updated.\n    \n# [id is required]\n```\n\n```php\n# Bulk update\n\nES::type(\"my_type\")-\u003ebulk(function ($bulk){\n    $bulk-\u003eid(10)-\u003eupdate([\"title\" =\u003e \"Test document 1\",\"content\" =\u003e \"Sample content 1\"]);\n    $bulk-\u003eid(11)-\u003eupdate([\"title\" =\u003e \"Test document 2\",\"content\" =\u003e \"Sample content 2\"]);\n});\n```\n\n### Incrementing field\n\n```php\nES::type(\"my_type\")-\u003eid(3)-\u003eincrement(\"views\");\n    \n# Document has _id = 3 will be incremented by 1.\n\nES::type(\"my_type\")-\u003eid(3)-\u003eincrement(\"views\", 3);\n\n# Document has _id = 3 will be incremented by 3.\n\n# [id is required]\n```\n\n### Decrementing field\n\n```php\nES::type(\"my_type\")-\u003eid(3)-\u003edecrement(\"views\");\n    \n# Document has _id = 3 will be decremented by 1.\n    \nES::type(\"my_type\")-\u003eid(3)-\u003edecrement(\"views\", 3);\n    \n# Document has _id = 3 will be decremented by 3.\n\n# [id is required]\n```\n\n### Update using script\n\n```php\n# increment field by script\nES::type(\"my_type\")-\u003eid(3)-\u003escript(\n    \"ctx._source.$field += params.count\",\n    [\"count\" =\u003e 1]\n);\n    \n# add php tag to tags array list\nES::type(\"my_type\")-\u003eid(3)-\u003escript(\n    \"ctx._source.tags.add(params.tag)\",\n    [\"tag\" =\u003e \"php\"]\n);\n    \n# delete the doc if the tags field contain mongodb, otherwise it does nothing (noop)\nES::type(\"my_type\")-\u003eid(3)-\u003escript(\n    \"if (ctx._source.tags.contains(params.tag)) { ctx.op = 'delete' } else { ctx.op = 'none' }\",\n    [\"tag\" =\u003e \"mongodb\"]\n);\n```\n\n### Delete a document\n\n```php\nES::type(\"my_type\")-\u003eid(3)-\u003edelete();\n\n# Document has _id = 3 will be deleted.\n# [id is required]\n```\n\n```php\n# Bulk delete\nES::type(\"my_type\")-\u003ebulk(function ($bulk){\n    $bulk-\u003eid(10)-\u003edelete();\n    $bulk-\u003eid(11)-\u003edelete();\n});\n```\n\nReleases\n--------\nSee the [release page](https://github.com/matchory/elasticsearch/releases).\n\nAuthors\n-------\n[Basem Khirat](http://basemkhirat.com) - [basemkhirat@gmail.com](mailto:basemkhirat@gmail.com) - [@basemkhirat](https://twitter.com/basemkhirat)  \n[Moritz Friedrich](https://www.matchory.com) - [moritz@matchory.com](mailto:moritz@matchory.com)\n\nBugs, Suggestions and Contributions\n-----------------------------------\nThanks to [everyone](https://github.com/basemkhirat/elasticsearch/graphs/contributors) who has contributed to the\noriginal project and\n[everyone else](https://github.com/matchory/elasticsearch/graphs/contributors) who has contributed to this fork!  \nPlease use [Github](https://github.com/matchory/elasticsearch) for reporting bugs, and making comments or suggestions.\n\nIf you're interested in helping out, the most pressing issues would be modernizing the query builder to provide better\nsupport for Elasticsearch features as\nwell as completing the test suite!\n\nLicense\n-------\nMIT\n\n`Have a happy searching..`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatchory%2Felasticsearch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmatchory%2Felasticsearch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmatchory%2Felasticsearch/lists"}