├── .phpdoc └── template │ └── base.html.twig ├── .yamllint.yml ├── CNAME ├── Dockerfile ├── LICENSE ├── README.md ├── composer.json ├── docker-compose.yml ├── phpstan.dist.neon ├── phpunit.xml.dist.bak └── src ├── Client.php ├── Contracts ├── BatchesQuery.php ├── BatchesResults.php ├── CancelTasksQuery.php ├── Data.php ├── DeleteTasksQuery.php ├── DocumentsQuery.php ├── DocumentsResults.php ├── Endpoint.php ├── FacetSearchQuery.php ├── FederationOptions.php ├── Http.php ├── HybridSearchOptions.php ├── Index │ ├── Embedders.php │ ├── Faceting.php │ ├── Settings.php │ ├── Synonyms.php │ └── TypoTolerance.php ├── IndexesQuery.php ├── IndexesResults.php ├── KeysQuery.php ├── KeysResults.php ├── MultiSearchFederation.php ├── NetworkResults.php ├── SearchQuery.php ├── SimilarDocumentsQuery.php ├── TasksQuery.php └── TasksResults.php ├── Endpoints ├── Batches.php ├── Delegates │ ├── HandlesBatches.php │ ├── HandlesDocuments.php │ ├── HandlesDumps.php │ ├── HandlesIndex.php │ ├── HandlesKeys.php │ ├── HandlesMultiSearch.php │ ├── HandlesNetwork.php │ ├── HandlesSettings.php │ ├── HandlesSnapshots.php │ ├── HandlesSystem.php │ ├── HandlesTasks.php │ └── TasksQueryTrait.php ├── Dumps.php ├── Health.php ├── Indexes.php ├── Keys.php ├── Network.php ├── Snapshots.php ├── Stats.php ├── Tasks.php ├── TenantToken.php └── Version.php ├── Exceptions ├── ApiException.php ├── CommunicationException.php ├── ExceptionInterface.php ├── InvalidArgumentException.php ├── InvalidResponseBodyException.php ├── JsonDecodingException.php ├── JsonEncodingException.php └── TimeOutException.php ├── Http ├── Client.php └── Serialize │ ├── Json.php │ └── SerializerInterface.php ├── Meilisearch.php └── Search ├── FacetSearchResult.php ├── SearchResult.php └── SimilarDocumentsSearchResult.php /.phpdoc/template/base.html.twig: -------------------------------------------------------------------------------- 1 | {# 2 | Extending the default phpDocumentor layout 3 | https://github.com/phpDocumentor/phpDocumentor/blob/master/data/templates/default/layout.html.twig 4 | #} 5 | 6 | {% extends 'layout.html.twig' %} 7 | 8 | {% block javascripts %} 9 | {{ parent() }} 10 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | extends: default 2 | ignore: | 3 | vendor 4 | rules: 5 | comments-indentation: disable 6 | line-length: disable 7 | document-start: disable 8 | brackets: disable 9 | truthy: disable 10 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | php-sdk-docs.meilisearch.com 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.2 2 | 3 | RUN apt-get update && apt-get install -y \ 4 | git \ 5 | unzip \ 6 | && rm -rf /var/lib/apt/lists/* 7 | 8 | COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2025 Meili SAS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Meilisearch-PHP 3 |

4 | 5 |

Meilisearch PHP

6 | 7 |

8 | Meilisearch | 9 | Meilisearch Cloud | 10 | Documentation | 11 | Discord | 12 | Roadmap | 13 | Website | 14 | FAQ 15 |

16 | 17 |

18 | Codecov coverage 19 | Latest Stable Version 20 | Test 21 | License 22 | Bors enabled 23 |

24 | 25 |

⚡ The Meilisearch API client written for PHP 🐘

26 | 27 | **Meilisearch PHP** is the Meilisearch API client for PHP developers. 28 | 29 | **Meilisearch** is an open-source search engine. [Learn more about Meilisearch.](https://github.com/meilisearch/Meilisearch) 30 | 31 | ## Table of Contents 32 | 33 | - [📖 Documentation](#-documentation) 34 | - [🔧 Installation](#-installation) 35 | - [🚀 Getting started](#-getting-started) 36 | - [🤖 Compatibility with Meilisearch](#-compatibility-with-meilisearch) 37 | - [💡 Learn more](#-learn-more) 38 | - [🧰 HTTP Client Compatibilities](#-http-client-compatibilities) 39 | - [Customize your HTTP Client](#customize-your-http-client) 40 | - [⚙️ Contributing](#️-contributing) 41 | 42 | ## 📖 Documentation 43 | 44 | 45 | To learn more about Meilisearch PHP, refer to the in-depth [Meilisearch PHP Documentation](https://php-sdk.meilisearch.com). To learn more about Meilisearch in general, refer to our [documentation](https://www.meilisearch.com/docs/learn/getting_started/quick_start) or our [API reference](https://www.meilisearch.com/docs/reference/api/overview). 46 | 47 | ## 🔧 Installation 48 | 49 | To get started, simply require the project using [Composer](https://getcomposer.org/).
50 | You will also need to install packages that "provide" [`psr/http-client-implementation`](https://packagist.org/providers/psr/http-client-implementation) and [`psr/http-factory-implementation`](https://packagist.org/providers/psr/http-factory-implementation).
51 | A list with compatible HTTP clients and client adapters can be found at [php-http.org](http://docs.php-http.org/en/latest/clients.html). 52 | 53 | **If you don't know which HTTP client to use, we recommend using Guzzle 7**: 54 | 55 | ```bash 56 | composer require meilisearch/meilisearch-php guzzlehttp/guzzle http-interop/http-factory-guzzle:^1.0 57 | ``` 58 | 59 | Here is an example of installation with the `symfony/http-client`: 60 | 61 | ```bash 62 | composer require meilisearch/meilisearch-php symfony/http-client nyholm/psr7:^1.0 63 | ``` 64 | 65 | 💡 *More HTTP client installations compatible with this package can be found [in this section](#-http-client-compatibilities).* 66 | 67 | ### Run Meilisearch 68 | 69 | ⚡️ **Launch, scale, and streamline in minutes with Meilisearch Cloud**—no maintenance, no commitment, cancel anytime. [Try it free now](https://cloud.meilisearch.com/login?utm_campaign=oss&utm_source=github&utm_medium=meilisearch-php). 70 | 71 | 🪨 Prefer to self-host? [Download and deploy](https://www.meilisearch.com/docs/learn/self_hosted/getting_started_with_self_hosted_meilisearch?utm_campaign=oss&utm_source=github&utm_medium=meilisearch-php) our fast, open-source search engine on your own infrastructure. 72 | 73 | ## 🚀 Getting started 74 | 75 | #### Add documents 76 | 77 | ```php 78 | index('movies'); 88 | 89 | $documents = [ 90 | ['id' => 1, 'title' => 'Carol', 'genres' => ['Romance, Drama']], 91 | ['id' => 2, 'title' => 'Wonder Woman', 'genres' => ['Action, Adventure']], 92 | ['id' => 3, 'title' => 'Life of Pi', 'genres' => ['Adventure, Drama']], 93 | ['id' => 4, 'title' => 'Mad Max: Fury Road', 'genres' => ['Adventure, Science Fiction']], 94 | ['id' => 5, 'title' => 'Moana', 'genres' => ['Fantasy, Action']], 95 | ['id' => 6, 'title' => 'Philadelphia', 'genres' => ['Drama']], 96 | ]; 97 | 98 | # If the index 'movies' does not exist, Meilisearch creates it when you first add the documents. 99 | $index->addDocuments($documents); // => { "uid": 0 } 100 | ``` 101 | 102 | With the `uid`, you can check the status (`enqueued`, `canceled`, `processing`, `succeeded` or `failed`) of your documents addition using the [task](https://www.meilisearch.com/docs/reference/api/tasks#status). 103 | 104 | #### Basic Search 105 | 106 | ```php 107 | // Meilisearch is typo-tolerant: 108 | $hits = $index->search('wondre woman')->getHits(); 109 | print_r($hits); 110 | ``` 111 | 112 | Output: 113 | 114 | ```php 115 | Array 116 | ( 117 | [0] => Array 118 | ( 119 | [id] => 2 120 | [title] => Wonder Woman 121 | [genres] => Array 122 | ( 123 | [0] => Action, Adventure 124 | ) 125 | ) 126 | ) 127 | ``` 128 | 129 | #### Custom Search 130 | 131 | All the supported options are described in the [search parameters](https://www.meilisearch.com/docs/reference/api/search#search-parameters) section of the documentation. 132 | 133 | 💡 **More about the `search()` method in [the Wiki](https://github.com/meilisearch/meilisearch-php/wiki/Search).** 134 | 135 | ```php 136 | $index->search( 137 | 'phil', 138 | [ 139 | 'attributesToHighlight' => ['*'], 140 | ] 141 | )->getRaw(); // Return in Array format 142 | ``` 143 | 144 | JSON output: 145 | 146 | ```json 147 | { 148 | "hits": [ 149 | { 150 | "id": 6, 151 | "title": "Philadelphia", 152 | "genre": ["Drama"], 153 | "_formatted": { 154 | "id": 6, 155 | "title": "Philadelphia", 156 | "genre": ["Drama"] 157 | } 158 | } 159 | ], 160 | "offset": 0, 161 | "limit": 20, 162 | "processingTimeMs": 0, 163 | "query": "phil" 164 | } 165 | ``` 166 | #### Custom Search With Filters 167 | 168 | If you want to enable filtering, you must add your attributes to the `filterableAttributes` index setting. 169 | 170 | ```php 171 | $index->updateFilterableAttributes([ 172 | 'id', 173 | 'genres' 174 | ]); 175 | ``` 176 | 177 | You only need to perform this operation once. 178 | 179 | Note that Meilisearch will rebuild your index whenever you update `filterableAttributes`. Depending on the size of your dataset, this might take time. You can track the process using the [tasks](https://www.meilisearch.com/docs/reference/api/tasks#get-tasks)). 180 | 181 | Then, you can perform the search: 182 | 183 | ```php 184 | $index->search( 185 | 'wonder', 186 | [ 187 | 'filter' => ['id > 1 AND genres = Action'] 188 | ] 189 | ); 190 | ``` 191 | 192 | ```json 193 | { 194 | "hits": [ 195 | { 196 | "id": 2, 197 | "title": "Wonder Woman", 198 | "genres": ["Action","Adventure"] 199 | } 200 | ], 201 | "offset": 0, 202 | "limit": 20, 203 | "estimatedTotalHits": 1, 204 | "processingTimeMs": 0, 205 | "query": "wonder" 206 | } 207 | ``` 208 | 209 | ## 🤖 Compatibility with Meilisearch 210 | 211 | This package guarantees compatibility with [version v1.x of Meilisearch](https://github.com/meilisearch/meilisearch/releases/latest), but some features may not be present. Please check the [issues](https://github.com/meilisearch/meilisearch-php/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22+label%3Aenhancement) for more info. 212 | 213 | ## 💡 Learn more 214 | 215 | The following sections in our main documentation website may interest you: 216 | 217 | - **Manipulate documents**: see the [API references](https://www.meilisearch.com/docs/reference/api/documents) or read more about [documents](https://www.meilisearch.com/docs/learn/core_concepts/documents). 218 | - **Search**: see the [API references](https://www.meilisearch.com/docs/reference/api/search) or follow our guide on [search parameters](https://www.meilisearch.com/docs/reference/api/search#search-parameters). 219 | - **Manage the indexes**: see the [API references](https://www.meilisearch.com/docs/reference/api/indexes) or read more about [indexes](https://www.meilisearch.com/docs/learn/core_concepts/indexes). 220 | - **Configure the index settings**: see the [API references](https://www.meilisearch.com/docs/reference/api/settings) or follow our guide on [settings parameters](https://www.meilisearch.com/docs/learn/configuration/settings). 221 | 222 | ## 🧰 HTTP Client Compatibilities 223 | 224 | You could use any [PSR-18](https://www.php-fig.org/psr/psr-18/) compatible client to use with this SDK. No additional configurations are required.
225 | A list of compatible HTTP clients and client adapters can be found at [php-http.org](http://docs.php-http.org/en/latest/clients.html). 226 | 227 | If you want to use this `meilisearch-php`: 228 | 229 | - with `guzzlehttp/guzzle` (Guzzle 7), run: 230 | 231 | ```bash 232 | composer require meilisearch/meilisearch-php guzzlehttp/guzzle http-interop/http-factory-guzzle:^1.0 233 | ``` 234 | 235 | - with `php-http/guzzle6-adapter` (Guzzle < 7), run: 236 | 237 | ```bash 238 | composer require meilisearch/meilisearch-php php-http/guzzle6-adapter:^2.0 http-interop/http-factory-guzzle:^1.0 239 | ``` 240 | 241 | - with `symfony/http-client`, run: 242 | 243 | ```bash 244 | composer require meilisearch/meilisearch-php symfony/http-client nyholm/psr7:^1.0 245 | ``` 246 | 247 | - with `php-http/curl-client`, run: 248 | 249 | ```bash 250 | composer require meilisearch/meilisearch-php php-http/curl-client nyholm/psr7:^1.0 251 | ``` 252 | 253 | - with `kriswallsmith/buzz`, run: 254 | 255 | ```bash 256 | composer require meilisearch/meilisearch-php kriswallsmith/buzz nyholm/psr7:^1.0 257 | ``` 258 | 259 | ### Customize your HTTP Client 260 | 261 | For some reason, you might want to pass a custom configuration to your own HTTP client.
262 | Make sure you have a [PSR-18](https://www.php-fig.org/psr/psr-18/) compatible client when you initialize the Meilisearch client. 263 | 264 | Following the example in the [Getting started](#-getting-started) section, with the Guzzle HTTP client: 265 | 266 | ```php 267 | new Client('http://127.0.0.1:7700', 'masterKey', new GuzzleHttpClient(['timeout' => 2])); 268 | ``` 269 | 270 | ## ⚙️ Contributing 271 | 272 | Any new contribution is more than welcome in this project! 273 | 274 | If you want to know more about the development workflow or want to contribute, please visit our [contributing guidelines](/CONTRIBUTING.md) for detailed instructions! 275 | 276 |
277 | 278 | **Meilisearch** provides and maintains many **SDKs and Integration tools** like this one. We want to provide everyone with an **amazing search experience for any kind of project**. If you want to contribute, make suggestions, or just know what's going on right now, visit us in the [integration-guides](https://github.com/meilisearch/integration-guides) repository. 279 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meilisearch/meilisearch-php", 3 | "description": "PHP wrapper for the Meilisearch API", 4 | "keywords": [ 5 | "meilisearch", 6 | "instant", 7 | "search", 8 | "api", 9 | "client", 10 | "php" 11 | ], 12 | "type": "library", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Clementine", 17 | "email": "clementine@meilisearch.com" 18 | } 19 | ], 20 | "minimum-stability": "stable", 21 | "require": { 22 | "php": "^7.4 || ^8.0", 23 | "ext-json": "*", 24 | "php-http/discovery": "^1.7", 25 | "psr/http-client": "^1.0" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "MeiliSearch\\": "src/", 30 | "Meilisearch\\": "src/" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "Tests\\": "tests/" 36 | } 37 | }, 38 | "suggest": { 39 | "guzzlehttp/guzzle": "Use Guzzle ^7 as HTTP client", 40 | "http-interop/http-factory-guzzle": "Factory for guzzlehttp/guzzle" 41 | }, 42 | "require-dev": { 43 | "phpunit/phpunit": "^9.5 || ^10.5", 44 | "php-cs-fixer/shim": "^3.59.3", 45 | "guzzlehttp/guzzle": "^7.8.1", 46 | "http-interop/http-factory-guzzle": "^1.2.0", 47 | "phpstan/phpstan": "^2.0", 48 | "phpstan/phpstan-phpunit": "^2.0", 49 | "phpstan/phpstan-deprecation-rules": "^2.0", 50 | "phpstan/phpstan-strict-rules": "^2.0" 51 | }, 52 | "scripts": { 53 | "lint": [ 54 | "./vendor/bin/php-cs-fixer fix --verbose --config=.php-cs-fixer.dist.php --using-cache=no --dry-run --diff" 55 | ], 56 | "lint:fix": [ 57 | "./vendor/bin/php-cs-fixer fix --verbose --config=.php-cs-fixer.dist.php --using-cache=no --diff" 58 | ], 59 | "phpstan": "./vendor/bin/phpstan", 60 | "test": ["sh scripts/tests.sh"] 61 | }, 62 | "config": { 63 | "allow-plugins": { 64 | "php-http/discovery": true 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | package: 3 | build: . 4 | tty: true 5 | stdin_open: true 6 | working_dir: /home/package 7 | environment: 8 | - MEILISEARCH_URL=http://meilisearch:7700 9 | depends_on: 10 | - meilisearch 11 | links: 12 | - meilisearch 13 | volumes: 14 | - ./:/home/package 15 | 16 | meilisearch: 17 | image: getmeili/meilisearch:latest 18 | ports: 19 | - "7700" 20 | environment: 21 | - MEILI_MASTER_KEY=masterKey 22 | - MEILI_NO_ANALYTICS=true 23 | -------------------------------------------------------------------------------- /phpstan.dist.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - vendor/phpstan/phpstan-phpunit/extension.neon 3 | - vendor/phpstan/phpstan-phpunit/rules.neon 4 | - vendor/phpstan/phpstan-deprecation-rules/rules.neon 5 | - vendor/phpstan/phpstan-strict-rules/rules.neon 6 | - vendor/phpstan/phpstan/conf/bleedingEdge.neon 7 | 8 | parameters: 9 | level: 5 10 | paths: 11 | - src 12 | - tests 13 | additionalConstructors: 14 | - PHPUnit\Framework\TestCase::setUp 15 | -------------------------------------------------------------------------------- /phpunit.xml.dist.bak: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests 14 | 15 | 16 | 17 | 18 | src/ 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | $clientAgents 46 | */ 47 | public function __construct( 48 | string $url, 49 | ?string $apiKey = null, 50 | ?ClientInterface $httpClient = null, 51 | ?RequestFactoryInterface $requestFactory = null, 52 | array $clientAgents = [], 53 | ?StreamFactoryInterface $streamFactory = null 54 | ) { 55 | $this->http = new MeilisearchClientAdapter($url, $apiKey, $httpClient, $requestFactory, $clientAgents, $streamFactory); 56 | $this->index = new Indexes($this->http); 57 | $this->health = new Health($this->http); 58 | $this->version = new Version($this->http); 59 | $this->stats = new Stats($this->http); 60 | $this->tasks = new Tasks($this->http); 61 | $this->batches = new Batches($this->http); 62 | $this->keys = new Keys($this->http); 63 | $this->dumps = new Dumps($this->http); 64 | $this->snapshots = new Snapshots($this->http); 65 | $this->tenantToken = new TenantToken($this->http, $apiKey); 66 | $this->network = new Network($this->http); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Contracts/BatchesQuery.php: -------------------------------------------------------------------------------- 1 | from = $from; 28 | 29 | return $this; 30 | } 31 | 32 | /** 33 | * @return $this 34 | */ 35 | public function setLimit(int $limit): self 36 | { 37 | $this->limit = $limit; 38 | 39 | return $this; 40 | } 41 | 42 | /** 43 | * @return $this 44 | */ 45 | public function setReverse(bool $reverse): self 46 | { 47 | $this->reverse = $reverse; 48 | 49 | return $this; 50 | } 51 | 52 | public function toArray(): array 53 | { 54 | return array_filter( 55 | array_merge( 56 | $this->baseArray(), 57 | [ 58 | 'from' => $this->from, 59 | 'limit' => $this->limit, 60 | 'reverse' => (null !== $this->reverse ? ($this->reverse ? 'true' : 'false') : null), 61 | ] 62 | ), 63 | static function ($item) { 64 | return null !== $item; 65 | } 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Contracts/BatchesResults.php: -------------------------------------------------------------------------------- 1 | from = $params['from'] ?? 0; 34 | $this->limit = $params['limit'] ?? 0; 35 | $this->next = $params['next'] ?? 0; 36 | $this->total = $params['total'] ?? 0; 37 | } 38 | 39 | /** 40 | * @return array 41 | */ 42 | public function getResults(): array 43 | { 44 | return $this->data; 45 | } 46 | 47 | /** 48 | * @return non-negative-int 49 | */ 50 | public function getNext(): int 51 | { 52 | return $this->next; 53 | } 54 | 55 | /** 56 | * @return non-negative-int 57 | */ 58 | public function getLimit(): int 59 | { 60 | return $this->limit; 61 | } 62 | 63 | /** 64 | * @return non-negative-int 65 | */ 66 | public function getFrom(): int 67 | { 68 | return $this->from; 69 | } 70 | 71 | /** 72 | * @return non-negative-int 73 | */ 74 | public function getTotal(): int 75 | { 76 | return $this->total; 77 | } 78 | 79 | public function toArray(): array 80 | { 81 | return [ 82 | 'results' => $this->data, 83 | 'next' => $this->next, 84 | 'limit' => $this->limit, 85 | 'from' => $this->from, 86 | 'total' => $this->total, 87 | ]; 88 | } 89 | 90 | public function count(): int 91 | { 92 | return \count($this->data); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Contracts/CancelTasksQuery.php: -------------------------------------------------------------------------------- 1 | data = $data; 14 | } 15 | 16 | public function offsetSet($offset, $value): void 17 | { 18 | $this->data[$offset] = $value; 19 | } 20 | 21 | public function offsetExists($offset): bool 22 | { 23 | return isset($this->data[$offset]) || \array_key_exists($offset, $this->data); 24 | } 25 | 26 | public function offsetUnset($offset): void 27 | { 28 | unset($this->data[$offset]); 29 | } 30 | 31 | #[\ReturnTypeWillChange] 32 | public function offsetGet($offset) 33 | { 34 | if (isset($this->data[$offset])) { 35 | return $this->data[$offset]; 36 | } 37 | 38 | return null; 39 | } 40 | 41 | public function count(): int 42 | { 43 | return \count($this->data); 44 | } 45 | 46 | public function getIterator(): \ArrayIterator 47 | { 48 | return new \ArrayIterator($this->data); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Contracts/DeleteTasksQuery.php: -------------------------------------------------------------------------------- 1 | |null 15 | */ 16 | private ?array $canceledBy = null; 17 | 18 | /** 19 | * @param non-empty-list $canceledBy 20 | * 21 | * @return $this 22 | */ 23 | public function setCanceledBy(array $canceledBy): self 24 | { 25 | $this->canceledBy = $canceledBy; 26 | 27 | return $this; 28 | } 29 | 30 | public function toArray(): array 31 | { 32 | return array_filter( 33 | array_merge( 34 | $this->baseArray(), 35 | ['canceledBy' => $this->formatArray($this->canceledBy)] 36 | ), static function ($item) { return null !== $item; } 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Contracts/DocumentsQuery.php: -------------------------------------------------------------------------------- 1 | |null 21 | */ 22 | private ?array $fields = null; 23 | 24 | /** 25 | * @var list>|null 26 | */ 27 | private ?array $filter = null; 28 | 29 | private ?bool $retrieveVectors = null; 30 | 31 | /** 32 | * @var list|null 33 | */ 34 | private ?array $ids = null; 35 | 36 | /** 37 | * @param non-negative-int $offset 38 | * 39 | * @return $this 40 | */ 41 | public function setOffset(int $offset): self 42 | { 43 | $this->offset = $offset; 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * @param non-negative-int $limit 50 | * 51 | * @return $this 52 | */ 53 | public function setLimit(int $limit): self 54 | { 55 | $this->limit = $limit; 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * @param non-empty-list $fields 62 | * 63 | * @return $this 64 | */ 65 | public function setFields(array $fields): self 66 | { 67 | $this->fields = $fields; 68 | 69 | return $this; 70 | } 71 | 72 | /** 73 | * Sets the filter for the DocumentsQuery. 74 | * 75 | * @param list> $filter a filter expression written as an array of strings 76 | * 77 | * @return $this 78 | */ 79 | public function setFilter(array $filter): self 80 | { 81 | $this->filter = $filter; 82 | 83 | return $this; 84 | } 85 | 86 | /** 87 | * @param bool|null $retrieveVectors boolean value to show _vector details 88 | * 89 | * @return $this 90 | */ 91 | public function setRetrieveVectors(?bool $retrieveVectors): self 92 | { 93 | $this->retrieveVectors = $retrieveVectors; 94 | 95 | return $this; 96 | } 97 | 98 | /** 99 | * @param list $ids Array of document IDs 100 | * 101 | * @return $this 102 | */ 103 | public function setIds(array $ids): self 104 | { 105 | $this->ids = $ids; 106 | 107 | return $this; 108 | } 109 | 110 | /** 111 | * Checks if the $filter attribute has been set. 112 | * 113 | * @return bool true when filter contains at least a non-empty array 114 | */ 115 | public function hasFilter(): bool 116 | { 117 | return null !== $this->filter; 118 | } 119 | 120 | /** 121 | * @return array{ 122 | * offset?: non-negative-int, 123 | * limit?: non-negative-int, 124 | * fields?: non-empty-list|non-empty-string, 125 | * filter?: list>, 126 | * retrieveVectors?: 'true'|'false', 127 | * ids?: string 128 | * } 129 | */ 130 | public function toArray(): array 131 | { 132 | return array_filter([ 133 | 'offset' => $this->offset, 134 | 'limit' => $this->limit, 135 | 'fields' => $this->getFields(), 136 | 'filter' => $this->filter, 137 | 'retrieveVectors' => (null !== $this->retrieveVectors ? ($this->retrieveVectors ? 'true' : 'false') : null), 138 | 'ids' => ($this->ids ?? []) !== [] ? implode(',', $this->ids) : null, 139 | ], static function ($item) { return null !== $item; }); 140 | } 141 | 142 | /** 143 | * Prepares fields for request 144 | * Fix for 1.2 document/fetch. 145 | * 146 | * @see https://github.com/meilisearch/meilisearch-php/issues/522 147 | * 148 | * @return array|string|null 149 | */ 150 | private function getFields() 151 | { 152 | if (null === $this->fields) { 153 | return null; 154 | } 155 | 156 | if (null !== $this->filter) { 157 | return $this->fields; 158 | } 159 | 160 | return implode(',', $this->fields); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/Contracts/DocumentsResults.php: -------------------------------------------------------------------------------- 1 | offset = $params['offset']; 18 | $this->limit = $params['limit']; 19 | $this->total = $params['total'] ?? 0; 20 | } 21 | 22 | /** 23 | * @return array 24 | */ 25 | public function getResults(): array 26 | { 27 | return $this->data; 28 | } 29 | 30 | public function getOffset(): int 31 | { 32 | return $this->offset; 33 | } 34 | 35 | public function getLimit(): int 36 | { 37 | return $this->limit; 38 | } 39 | 40 | public function getTotal(): int 41 | { 42 | return $this->total; 43 | } 44 | 45 | public function toArray(): array 46 | { 47 | return [ 48 | 'results' => $this->data, 49 | 'offset' => $this->offset, 50 | 'limit' => $this->limit, 51 | 'total' => $this->total, 52 | ]; 53 | } 54 | 55 | public function count(): int 56 | { 57 | return \count($this->data); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Contracts/Endpoint.php: -------------------------------------------------------------------------------- 1 | http = $http; 16 | $this->apiKey = $apiKey; 17 | } 18 | 19 | public function show(): ?array 20 | { 21 | return $this->http->get(static::PATH); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Contracts/FacetSearchQuery.php: -------------------------------------------------------------------------------- 1 | >|null 23 | */ 24 | private ?array $filter = null; 25 | 26 | /** 27 | * @var 'last'|'all'|'frequency'|null 28 | */ 29 | private ?string $matchingStrategy = null; 30 | 31 | /** 32 | * @var non-empty-list|null 33 | */ 34 | private ?array $attributesToSearchOn = null; 35 | 36 | private ?bool $exhaustiveFacetsCount = null; 37 | 38 | /** 39 | * @return $this 40 | */ 41 | public function setFacetName(string $facetName): self 42 | { 43 | $this->facetName = $facetName; 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * @return $this 50 | */ 51 | public function setFacetQuery(string $facetQuery): self 52 | { 53 | $this->facetQuery = $facetQuery; 54 | 55 | return $this; 56 | } 57 | 58 | /** 59 | * @return $this 60 | */ 61 | public function setQuery(string $q): self 62 | { 63 | $this->q = $q; 64 | 65 | return $this; 66 | } 67 | 68 | /** 69 | * @param list> $filter 70 | * 71 | * @return $this 72 | */ 73 | public function setFilter(array $filter): self 74 | { 75 | $this->filter = $filter; 76 | 77 | return $this; 78 | } 79 | 80 | /** 81 | * @param 'last'|'all'|'frequency' $matchingStrategy 82 | * 83 | * @return $this 84 | */ 85 | public function setMatchingStrategy(string $matchingStrategy): self 86 | { 87 | $this->matchingStrategy = $matchingStrategy; 88 | 89 | return $this; 90 | } 91 | 92 | /** 93 | * @param non-empty-list $attributesToSearchOn 94 | * 95 | * @return $this 96 | */ 97 | public function setAttributesToSearchOn(array $attributesToSearchOn): self 98 | { 99 | $this->attributesToSearchOn = $attributesToSearchOn; 100 | 101 | return $this; 102 | } 103 | 104 | /** 105 | * @return $this 106 | */ 107 | public function setExhaustiveFacetsCount(bool $exhaustiveFacetsCount): self 108 | { 109 | $this->exhaustiveFacetsCount = $exhaustiveFacetsCount; 110 | 111 | return $this; 112 | } 113 | 114 | /** 115 | * @return array{ 116 | * facetName?: non-empty-string, 117 | * facetQuery?: non-empty-string, 118 | * q?: string, 119 | * filter?: list>, 120 | * matchingStrategy?: 'last'|'all'|'frequency'|null, 121 | * attributesToSearchOn?: non-empty-list, 122 | * exhaustiveFacetsCount?: bool 123 | * } 124 | */ 125 | public function toArray(): array 126 | { 127 | return array_filter([ 128 | 'facetName' => $this->facetName, 129 | 'facetQuery' => $this->facetQuery, 130 | 'q' => $this->q, 131 | 'filter' => $this->filter, 132 | 'matchingStrategy' => $this->matchingStrategy, 133 | 'attributesToSearchOn' => $this->attributesToSearchOn, 134 | 'exhaustiveFacetsCount' => $this->exhaustiveFacetsCount, 135 | ], static function ($item) { return null !== $item; }); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Contracts/FederationOptions.php: -------------------------------------------------------------------------------- 1 | weight = $weight; 21 | 22 | return $this; 23 | } 24 | 25 | /** 26 | * @param non-empty-string $remote 27 | * 28 | * @return $this 29 | */ 30 | public function setRemote(string $remote): self 31 | { 32 | $this->remote = $remote; 33 | 34 | return $this; 35 | } 36 | 37 | /** 38 | * @return array{ 39 | * weight?: float, 40 | * remote?: non-empty-string, 41 | * } 42 | */ 43 | public function toArray(): array 44 | { 45 | return array_filter([ 46 | 'weight' => $this->weight, 47 | 'remote' => $this->remote, 48 | ], static function ($item) { return null !== $item; }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Contracts/Http.php: -------------------------------------------------------------------------------- 1 | semanticRatio = $ratio; 22 | 23 | return $this; 24 | } 25 | 26 | /** 27 | * @param non-empty-string $embedder 28 | * 29 | * @return $this 30 | */ 31 | public function setEmbedder(string $embedder): self 32 | { 33 | $this->embedder = $embedder; 34 | 35 | return $this; 36 | } 37 | 38 | /** 39 | * @return array{ 40 | * semanticRatio?: float, 41 | * embedder?: non-empty-string 42 | * } 43 | */ 44 | public function toArray(): array 45 | { 46 | return array_filter([ 47 | 'semanticRatio' => $this->semanticRatio, 48 | 'embedder' => $this->embedder, 49 | ], static function ($item) { return null !== $item; }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Contracts/Index/Embedders.php: -------------------------------------------------------------------------------- 1 | getIterator()->getArrayCopy(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Contracts/Index/Faceting.php: -------------------------------------------------------------------------------- 1 | getIterator()->getArrayCopy(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Contracts/Index/Settings.php: -------------------------------------------------------------------------------- 1 | getIterator()->getArrayCopy(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Contracts/Index/Synonyms.php: -------------------------------------------------------------------------------- 1 | getIterator()->getArrayCopy(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Contracts/Index/TypoTolerance.php: -------------------------------------------------------------------------------- 1 | getIterator()->getArrayCopy(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Contracts/IndexesQuery.php: -------------------------------------------------------------------------------- 1 | offset = $offset; 27 | 28 | return $this; 29 | } 30 | 31 | /** 32 | * @param non-negative-int $limit 33 | * 34 | * @return $this 35 | */ 36 | public function setLimit(int $limit): self 37 | { 38 | $this->limit = $limit; 39 | 40 | return $this; 41 | } 42 | 43 | /** 44 | * @return array{ 45 | * offset?: non-negative-int, 46 | * limit?: non-negative-int 47 | * } 48 | */ 49 | public function toArray(): array 50 | { 51 | return array_filter([ 52 | 'offset' => $this->offset, 53 | 'limit' => $this->limit, 54 | ], static function ($item) { return null !== $item; }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Contracts/IndexesResults.php: -------------------------------------------------------------------------------- 1 | offset = $params['offset']; 20 | $this->limit = $params['limit']; 21 | $this->total = $params['total'] ?? 0; 22 | } 23 | 24 | /** 25 | * @return array 26 | */ 27 | public function getResults(): array 28 | { 29 | return $this->data; 30 | } 31 | 32 | public function getOffset(): int 33 | { 34 | return $this->offset; 35 | } 36 | 37 | public function getLimit(): int 38 | { 39 | return $this->limit; 40 | } 41 | 42 | public function getTotal(): int 43 | { 44 | return $this->total; 45 | } 46 | 47 | public function toArray(): array 48 | { 49 | return [ 50 | 'results' => $this->data, 51 | 'offset' => $this->offset, 52 | 'limit' => $this->limit, 53 | 'total' => $this->total, 54 | ]; 55 | } 56 | 57 | public function count(): int 58 | { 59 | return \count($this->data); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Contracts/KeysQuery.php: -------------------------------------------------------------------------------- 1 | offset = $offset; 27 | 28 | return $this; 29 | } 30 | 31 | /** 32 | * @param non-negative-int $limit 33 | * 34 | * @return $this 35 | */ 36 | public function setLimit(int $limit): self 37 | { 38 | $this->limit = $limit; 39 | 40 | return $this; 41 | } 42 | 43 | /** 44 | * @return array{ 45 | * offset?: non-negative-int, 46 | * limit?: non-negative-int 47 | * } 48 | */ 49 | public function toArray(): array 50 | { 51 | return array_filter([ 52 | 'offset' => $this->offset, 53 | 'limit' => $this->limit, 54 | ], static function ($item) { return null !== $item; }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Contracts/KeysResults.php: -------------------------------------------------------------------------------- 1 | offset = $params['offset']; 18 | $this->limit = $params['limit']; 19 | $this->total = $params['total'] ?? 0; 20 | } 21 | 22 | /** 23 | * @return array 24 | */ 25 | public function getResults(): array 26 | { 27 | return $this->data; 28 | } 29 | 30 | public function getOffset(): int 31 | { 32 | return $this->offset; 33 | } 34 | 35 | public function getLimit(): int 36 | { 37 | return $this->limit; 38 | } 39 | 40 | public function getTotal(): int 41 | { 42 | return $this->total; 43 | } 44 | 45 | public function toArray(): array 46 | { 47 | return [ 48 | 'results' => $this->data, 49 | 'offset' => $this->offset, 50 | 'limit' => $this->limit, 51 | 'total' => $this->total, 52 | ]; 53 | } 54 | 55 | public function count(): int 56 | { 57 | return \count($this->data); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Contracts/MultiSearchFederation.php: -------------------------------------------------------------------------------- 1 | >|null 21 | */ 22 | private ?array $facetsByIndex = null; 23 | 24 | /** 25 | * @var array{maxValuesPerFacet: positive-int}|null 26 | */ 27 | private ?array $mergeFacets = null; 28 | 29 | /** 30 | * @param non-negative-int $limit 31 | * 32 | * @return $this 33 | */ 34 | public function setLimit(int $limit): self 35 | { 36 | $this->limit = $limit; 37 | 38 | return $this; 39 | } 40 | 41 | /** 42 | * @param non-negative-int $offset 43 | * 44 | * @return $this 45 | */ 46 | public function setOffset(int $offset): self 47 | { 48 | $this->offset = $offset; 49 | 50 | return $this; 51 | } 52 | 53 | /** 54 | * @param array> $facetsByIndex 55 | * 56 | * @return $this 57 | */ 58 | public function setFacetsByIndex(array $facetsByIndex): self 59 | { 60 | $this->facetsByIndex = $facetsByIndex; 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * @param array{maxValuesPerFacet: positive-int} $mergeFacets 67 | * 68 | * @return $this 69 | */ 70 | public function setMergeFacets(array $mergeFacets): self 71 | { 72 | $this->mergeFacets = $mergeFacets; 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * @return array{ 79 | * limit?: non-negative-int, 80 | * offset?: non-negative-int, 81 | * facetsByIndex?: array>, 82 | * mergeFacets?: array{maxValuesPerFacet: positive-int}, 83 | * } 84 | */ 85 | public function toArray(): array 86 | { 87 | return array_filter([ 88 | 'limit' => $this->limit, 89 | 'offset' => $this->offset, 90 | 'facetsByIndex' => $this->facetsByIndex, 91 | 'mergeFacets' => $this->mergeFacets, 92 | ], static function ($item) { return null !== $item; }); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Contracts/NetworkResults.php: -------------------------------------------------------------------------------- 1 | a mapping of remote node IDs to their connection details 16 | */ 17 | private array $remotes; 18 | 19 | /** 20 | * @param array{ 21 | * self?: non-empty-string, 22 | * remotes?: array 23 | * } $params 24 | */ 25 | public function __construct(array $params) 26 | { 27 | parent::__construct($params); 28 | 29 | $this->self = $params['self'] ?? ''; 30 | $this->remotes = $params['remotes'] ?? []; 31 | } 32 | 33 | /** 34 | * @return non-empty-string the identifier for the local node 35 | */ 36 | public function getSelf(): string 37 | { 38 | return $this->self; 39 | } 40 | 41 | /** 42 | * @return array 43 | */ 44 | public function getRemotes(): array 45 | { 46 | return $this->remotes; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Contracts/SearchQuery.php: -------------------------------------------------------------------------------- 1 | >|null 18 | */ 19 | private ?array $filter = null; 20 | 21 | /** 22 | * @var list|null 23 | */ 24 | private ?array $locales = null; 25 | 26 | /** 27 | * @var list|null 28 | */ 29 | private ?array $attributesToRetrieve = null; 30 | 31 | /** 32 | * @var list|null 33 | */ 34 | private ?array $attributesToCrop = null; 35 | 36 | /** 37 | * @var positive-int|null 38 | */ 39 | private ?int $cropLength = null; 40 | 41 | /** 42 | * @var list|null 43 | */ 44 | private ?array $attributesToHighlight = null; 45 | 46 | private ?string $cropMarker = null; 47 | 48 | private ?string $highlightPreTag = null; 49 | 50 | private ?string $highlightPostTag = null; 51 | 52 | /** 53 | * @var list|null 54 | */ 55 | private ?array $facets = null; 56 | 57 | private ?bool $showMatchesPosition = null; 58 | 59 | /** 60 | * @var list|null 61 | */ 62 | private ?array $sort = null; 63 | 64 | /** 65 | * @var 'last'|'all'|'frequency'|null 66 | */ 67 | private ?string $matchingStrategy = null; 68 | 69 | /** 70 | * @var non-negative-int|null 71 | */ 72 | private ?int $offset = null; 73 | 74 | /** 75 | * @var non-negative-int|null 76 | */ 77 | private ?int $limit = null; 78 | 79 | /** 80 | * @var non-negative-int|null 81 | */ 82 | private ?int $hitsPerPage = null; 83 | 84 | /** 85 | * @var non-negative-int|null 86 | */ 87 | private ?int $page = null; 88 | 89 | /** 90 | * @var non-empty-list>|null 91 | */ 92 | private ?array $vector = null; 93 | 94 | private ?HybridSearchOptions $hybrid = null; 95 | 96 | /** 97 | * @var non-empty-list|null 98 | */ 99 | private ?array $attributesToSearchOn = null; 100 | 101 | private ?bool $showRankingScore = null; 102 | 103 | private ?bool $showRankingScoreDetails = null; 104 | 105 | private ?float $rankingScoreThreshold = null; 106 | 107 | /** 108 | * @var non-empty-string|null 109 | */ 110 | private ?string $distinct = null; 111 | 112 | private ?FederationOptions $federationOptions = null; 113 | 114 | /** 115 | * @return $this 116 | */ 117 | public function setQuery(string $q): self 118 | { 119 | $this->q = $q; 120 | 121 | return $this; 122 | } 123 | 124 | /** 125 | * @param list> $filter 126 | * 127 | * @return $this 128 | */ 129 | public function setFilter(array $filter): self 130 | { 131 | $this->filter = $filter; 132 | 133 | return $this; 134 | } 135 | 136 | /** 137 | * @param list $locales 138 | * 139 | * @return $this 140 | */ 141 | public function setLocales(array $locales): self 142 | { 143 | $this->locales = $locales; 144 | 145 | return $this; 146 | } 147 | 148 | /** 149 | * @return $this 150 | */ 151 | public function setAttributesToRetrieve(array $attributesToRetrieve): self 152 | { 153 | $this->attributesToRetrieve = $attributesToRetrieve; 154 | 155 | return $this; 156 | } 157 | 158 | /** 159 | * @return $this 160 | */ 161 | public function setAttributesToCrop(array $attributesToCrop): self 162 | { 163 | $this->attributesToCrop = $attributesToCrop; 164 | 165 | return $this; 166 | } 167 | 168 | /** 169 | * @param positive-int|null $cropLength 170 | * 171 | * @return $this 172 | */ 173 | public function setCropLength(?int $cropLength): self 174 | { 175 | $this->cropLength = $cropLength; 176 | 177 | return $this; 178 | } 179 | 180 | /** 181 | * @param list $attributesToHighlight 182 | * 183 | * @return $this 184 | */ 185 | public function setAttributesToHighlight(array $attributesToHighlight): self 186 | { 187 | $this->attributesToHighlight = $attributesToHighlight; 188 | 189 | return $this; 190 | } 191 | 192 | /** 193 | * @return $this 194 | */ 195 | public function setCropMarker(string $cropMarker): self 196 | { 197 | $this->cropMarker = $cropMarker; 198 | 199 | return $this; 200 | } 201 | 202 | /** 203 | * @return $this 204 | */ 205 | public function setHighlightPreTag(string $highlightPreTag): self 206 | { 207 | $this->highlightPreTag = $highlightPreTag; 208 | 209 | return $this; 210 | } 211 | 212 | /** 213 | * @return $this 214 | */ 215 | public function setHighlightPostTag(string $highlightPostTag): self 216 | { 217 | $this->highlightPostTag = $highlightPostTag; 218 | 219 | return $this; 220 | } 221 | 222 | /** 223 | * @param list $facets 224 | * 225 | * @return $this 226 | */ 227 | public function setFacets(array $facets): self 228 | { 229 | $this->facets = $facets; 230 | 231 | return $this; 232 | } 233 | 234 | /** 235 | * @return $this 236 | */ 237 | public function setShowMatchesPosition(?bool $showMatchesPosition): self 238 | { 239 | $this->showMatchesPosition = $showMatchesPosition; 240 | 241 | return $this; 242 | } 243 | 244 | /** 245 | * @return $this 246 | */ 247 | public function setShowRankingScore(?bool $showRankingScore): self 248 | { 249 | $this->showRankingScore = $showRankingScore; 250 | 251 | return $this; 252 | } 253 | 254 | /** 255 | * This is an EXPERIMENTAL feature, which may break without a major version. 256 | * It's available after Meilisearch v1.3. 257 | * To enable it properly and use ranking scoring details its required to opt-in through the /experimental-features route. 258 | * 259 | * More info: https://www.meilisearch.com/docs/reference/api/experimental-features 260 | * 261 | * @param bool $showRankingScoreDetails whether the feature is enabled or not 262 | * 263 | * @return $this 264 | */ 265 | public function setShowRankingScoreDetails(?bool $showRankingScoreDetails): self 266 | { 267 | $this->showRankingScoreDetails = $showRankingScoreDetails; 268 | 269 | return $this; 270 | } 271 | 272 | /** 273 | * @return $this 274 | */ 275 | public function setRankingScoreThreshold(?float $rankingScoreThreshold): self 276 | { 277 | $this->rankingScoreThreshold = $rankingScoreThreshold; 278 | 279 | return $this; 280 | } 281 | 282 | /** 283 | * @param non-empty-string|null $distinct 284 | * 285 | * @return $this 286 | */ 287 | public function setDistinct(?string $distinct): self 288 | { 289 | $this->distinct = $distinct; 290 | 291 | return $this; 292 | } 293 | 294 | /** 295 | * @return $this 296 | */ 297 | public function setSort(array $sort): self 298 | { 299 | $this->sort = $sort; 300 | 301 | return $this; 302 | } 303 | 304 | /** 305 | * @param 'last'|'all'|'frequency' $matchingStrategy 306 | * 307 | * @return $this 308 | */ 309 | public function setMatchingStrategy(string $matchingStrategy): self 310 | { 311 | $this->matchingStrategy = $matchingStrategy; 312 | 313 | return $this; 314 | } 315 | 316 | /** 317 | * @param non-negative-int|null $offset 318 | * 319 | * @return $this 320 | */ 321 | public function setOffset(?int $offset): self 322 | { 323 | $this->offset = $offset; 324 | 325 | return $this; 326 | } 327 | 328 | /** 329 | * @param non-negative-int|null $limit 330 | * 331 | * @return $this 332 | */ 333 | public function setLimit(?int $limit): self 334 | { 335 | $this->limit = $limit; 336 | 337 | return $this; 338 | } 339 | 340 | /** 341 | * @param non-negative-int|null $hitsPerPage 342 | * 343 | * @return $this 344 | */ 345 | public function setHitsPerPage(?int $hitsPerPage): self 346 | { 347 | $this->hitsPerPage = $hitsPerPage; 348 | 349 | return $this; 350 | } 351 | 352 | /** 353 | * @return $this 354 | */ 355 | public function setPage(?int $page): self 356 | { 357 | $this->page = $page; 358 | 359 | return $this; 360 | } 361 | 362 | /** 363 | * @return $this 364 | */ 365 | public function setIndexUid(string $uid): self 366 | { 367 | $this->indexUid = $uid; 368 | 369 | return $this; 370 | } 371 | 372 | /** 373 | * This option is only available while doing a federated search. 374 | * If used in another context an error will be returned by Meilisearch. 375 | * 376 | * @return $this 377 | */ 378 | public function setFederationOptions(FederationOptions $federationOptions): self 379 | { 380 | $this->federationOptions = $federationOptions; 381 | 382 | return $this; 383 | } 384 | 385 | /** 386 | * This is an EXPERIMENTAL feature, which may break without a major version. 387 | * It's available from Meilisearch v1.3. 388 | * To enable it properly and use vector store capabilities it's required to activate it through the /experimental-features route. 389 | * 390 | * More info: https://www.meilisearch.com/docs/reference/api/experimental-features 391 | * 392 | * @param non-empty-list> $vector a multi-level array floats 393 | * 394 | * @return $this 395 | */ 396 | public function setVector(array $vector): self 397 | { 398 | $this->vector = $vector; 399 | 400 | return $this; 401 | } 402 | 403 | /** 404 | * This is an EXPERIMENTAL feature, which may break without a major version. 405 | * 406 | * Set hybrid search options 407 | * (new HybridSearchOptions()) 408 | * ->setSemanticRatio(0.8) 409 | * ->setEmbedder('manual'); 410 | * 411 | * @return $this 412 | */ 413 | public function setHybrid(HybridSearchOptions $hybridOptions): self 414 | { 415 | $this->hybrid = $hybridOptions; 416 | 417 | return $this; 418 | } 419 | 420 | /** 421 | * @param non-empty-list $attributesToSearchOn 422 | * 423 | * @return $this 424 | */ 425 | public function setAttributesToSearchOn(array $attributesToSearchOn): self 426 | { 427 | $this->attributesToSearchOn = $attributesToSearchOn; 428 | 429 | return $this; 430 | } 431 | 432 | /** 433 | * @return array{ 434 | * indexUid?: non-empty-string, 435 | * q?: string, 436 | * filter?: list>, 437 | * locales?: list, 438 | * attributesToRetrieve?: list, 439 | * attributesToCrop?: list, 440 | * cropLength?: positive-int, 441 | * attributesToHighlight?: list, 442 | * cropMarker?: string, 443 | * highlightPreTag?: string, 444 | * highlightPostTag?: string, 445 | * facets?: list, 446 | * showMatchesPosition?: bool, 447 | * sort?: list, 448 | * matchingStrategy?: 'last'|'all'|'frequency', 449 | * offset?: non-negative-int, 450 | * limit?: non-negative-int, 451 | * hitsPerPage?: non-negative-int, 452 | * page?: non-negative-int, 453 | * vector?: non-empty-list>, 454 | * hybrid?: array, 455 | * attributesToSearchOn?: non-empty-list, 456 | * showRankingScore?: bool, 457 | * showRankingScoreDetails?: bool, 458 | * rankingScoreThreshold?: float, 459 | * distinct?: non-empty-string, 460 | * federationOptions?: array 461 | * } 462 | */ 463 | public function toArray(): array 464 | { 465 | return array_filter([ 466 | 'indexUid' => $this->indexUid, 467 | 'q' => $this->q, 468 | 'filter' => $this->filter, 469 | 'locales' => $this->locales, 470 | 'attributesToRetrieve' => $this->attributesToRetrieve, 471 | 'attributesToCrop' => $this->attributesToCrop, 472 | 'cropLength' => $this->cropLength, 473 | 'attributesToHighlight' => $this->attributesToHighlight, 474 | 'cropMarker' => $this->cropMarker, 475 | 'highlightPreTag' => $this->highlightPreTag, 476 | 'highlightPostTag' => $this->highlightPostTag, 477 | 'facets' => $this->facets, 478 | 'showMatchesPosition' => $this->showMatchesPosition, 479 | 'sort' => $this->sort, 480 | 'matchingStrategy' => $this->matchingStrategy, 481 | 'offset' => $this->offset, 482 | 'limit' => $this->limit, 483 | 'hitsPerPage' => $this->hitsPerPage, 484 | 'page' => $this->page, 485 | 'vector' => $this->vector, 486 | 'hybrid' => null !== $this->hybrid ? $this->hybrid->toArray() : null, 487 | 'attributesToSearchOn' => $this->attributesToSearchOn, 488 | 'showRankingScore' => $this->showRankingScore, 489 | 'showRankingScoreDetails' => $this->showRankingScoreDetails, 490 | 'rankingScoreThreshold' => $this->rankingScoreThreshold, 491 | 'distinct' => $this->distinct, 492 | 'federationOptions' => null !== $this->federationOptions ? $this->federationOptions->toArray() : null, 493 | ], static function ($item) { return null !== $item; }); 494 | } 495 | } 496 | -------------------------------------------------------------------------------- /src/Contracts/SimilarDocumentsQuery.php: -------------------------------------------------------------------------------- 1 | |null 31 | */ 32 | private ?array $attributesToRetrieve = null; 33 | 34 | private ?bool $showRankingScore = null; 35 | 36 | private ?bool $showRankingScoreDetails = null; 37 | 38 | private ?bool $retrieveVectors = null; 39 | 40 | /** 41 | * @var array|string>|null 42 | */ 43 | private ?array $filter = null; 44 | 45 | /** 46 | * @var int|float|null 47 | */ 48 | private $rankingScoreThreshold; 49 | 50 | /** 51 | * @param int|string $id 52 | * @param non-empty-string $embedder 53 | */ 54 | public function __construct($id, string $embedder) 55 | { 56 | $this->id = $id; 57 | $this->embedder = $embedder; 58 | } 59 | 60 | /** 61 | * @param non-negative-int|null $offset 62 | * 63 | * @return $this 64 | */ 65 | public function setOffset(?int $offset): self 66 | { 67 | $this->offset = $offset; 68 | 69 | return $this; 70 | } 71 | 72 | /** 73 | * @param positive-int|null $limit 74 | * 75 | * @return $this 76 | */ 77 | public function setLimit(?int $limit): self 78 | { 79 | $this->limit = $limit; 80 | 81 | return $this; 82 | } 83 | 84 | /** 85 | * @param array|string> $filter an array of arrays representing filter conditions 86 | * 87 | * @return $this 88 | */ 89 | public function setFilter(array $filter): self 90 | { 91 | $this->filter = $filter; 92 | 93 | return $this; 94 | } 95 | 96 | /** 97 | * @param list $attributesToRetrieve an array of attribute names to retrieve 98 | * 99 | * @return $this 100 | */ 101 | public function setAttributesToRetrieve(array $attributesToRetrieve): self 102 | { 103 | $this->attributesToRetrieve = $attributesToRetrieve; 104 | 105 | return $this; 106 | } 107 | 108 | /** 109 | * @param bool|null $showRankingScore boolean value to show ranking score 110 | * 111 | * @return $this 112 | */ 113 | public function setShowRankingScore(?bool $showRankingScore): self 114 | { 115 | $this->showRankingScore = $showRankingScore; 116 | 117 | return $this; 118 | } 119 | 120 | /** 121 | * @param bool|null $showRankingScoreDetails boolean value to show ranking score details 122 | * 123 | * @return $this 124 | */ 125 | public function setShowRankingScoreDetails(?bool $showRankingScoreDetails): self 126 | { 127 | $this->showRankingScoreDetails = $showRankingScoreDetails; 128 | 129 | return $this; 130 | } 131 | 132 | /** 133 | * @param bool|null $retrieveVectors boolean value to show _vector details 134 | * 135 | * @return $this 136 | */ 137 | public function setRetrieveVectors(?bool $retrieveVectors): self 138 | { 139 | $this->retrieveVectors = $retrieveVectors; 140 | 141 | return $this; 142 | } 143 | 144 | /** 145 | * @param int|float|null $rankingScoreThreshold 146 | * 147 | * @return $this 148 | */ 149 | public function setRankingScoreThreshold($rankingScoreThreshold): self 150 | { 151 | $this->rankingScoreThreshold = $rankingScoreThreshold; 152 | 153 | return $this; 154 | } 155 | 156 | /** 157 | * @return array{ 158 | * id: int|string, 159 | * embedder: non-empty-string, 160 | * offset?: non-negative-int, 161 | * limit?: positive-int, 162 | * filter?: array|string>, 163 | * attributesToRetrieve?: list, 164 | * showRankingScore?: bool, 165 | * showRankingScoreDetails?: bool, 166 | * retrieveVectors?: bool, 167 | * rankingScoreThreshold?: int|float 168 | * } 169 | */ 170 | public function toArray(): array 171 | { 172 | return array_filter([ 173 | 'id' => $this->id, 174 | 'embedder' => $this->embedder, 175 | 'offset' => $this->offset, 176 | 'limit' => $this->limit, 177 | 'filter' => $this->filter, 178 | 'attributesToRetrieve' => $this->attributesToRetrieve, 179 | 'showRankingScore' => $this->showRankingScore, 180 | 'showRankingScoreDetails' => $this->showRankingScoreDetails, 181 | 'retrieveVectors' => $this->retrieveVectors, 182 | 'rankingScoreThreshold' => $this->rankingScoreThreshold, 183 | ], static function ($item) {return null !== $item; }); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/Contracts/TasksQuery.php: -------------------------------------------------------------------------------- 1 | |null 22 | */ 23 | private ?array $canceledBy = null; 24 | 25 | private ?int $batchUid = null; 26 | 27 | private ?bool $reverse = null; 28 | 29 | /** 30 | * @return $this 31 | */ 32 | public function setFrom(int $from): self 33 | { 34 | $this->from = $from; 35 | 36 | return $this; 37 | } 38 | 39 | /** 40 | * @param non-empty-list $canceledBy 41 | * 42 | * @return $this 43 | */ 44 | public function setCanceledBy(array $canceledBy): self 45 | { 46 | $this->canceledBy = $canceledBy; 47 | 48 | return $this; 49 | } 50 | 51 | /** 52 | * @return $this 53 | */ 54 | public function setLimit(int $limit): self 55 | { 56 | $this->limit = $limit; 57 | 58 | return $this; 59 | } 60 | 61 | /** 62 | * @return $this 63 | */ 64 | public function setBatchUid(int $batchUid): self 65 | { 66 | $this->batchUid = $batchUid; 67 | 68 | return $this; 69 | } 70 | 71 | public function setReverse(bool $reverse): self 72 | { 73 | $this->reverse = $reverse; 74 | 75 | return $this; 76 | } 77 | 78 | public function toArray(): array 79 | { 80 | return array_filter( 81 | array_merge( 82 | $this->baseArray(), 83 | [ 84 | 'from' => $this->from, 85 | 'limit' => $this->limit, 86 | 'canceledBy' => $this->formatArray($this->canceledBy), 87 | 'batchUid' => $this->batchUid, 88 | 'reverse' => (null !== $this->reverse ? ($this->reverse ? 'true' : 'false') : null), 89 | ] 90 | ), 91 | static function ($item) { 92 | return null !== $item; 93 | } 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Contracts/TasksResults.php: -------------------------------------------------------------------------------- 1 | from = $params['from'] ?? 0; 34 | $this->limit = $params['limit'] ?? 0; 35 | $this->next = $params['next'] ?? 0; 36 | $this->total = $params['total'] ?? 0; 37 | } 38 | 39 | /** 40 | * @return array 41 | */ 42 | public function getResults(): array 43 | { 44 | return $this->data; 45 | } 46 | 47 | /** 48 | * @return non-negative-int 49 | */ 50 | public function getNext(): int 51 | { 52 | return $this->next; 53 | } 54 | 55 | /** 56 | * @return non-negative-int 57 | */ 58 | public function getLimit(): int 59 | { 60 | return $this->limit; 61 | } 62 | 63 | /** 64 | * @return non-negative-int 65 | */ 66 | public function getFrom(): int 67 | { 68 | return $this->from; 69 | } 70 | 71 | /** 72 | * @return non-negative-int 73 | */ 74 | public function getTotal(): int 75 | { 76 | return $this->total; 77 | } 78 | 79 | public function toArray(): array 80 | { 81 | return [ 82 | 'results' => $this->data, 83 | 'next' => $this->next, 84 | 'limit' => $this->limit, 85 | 'from' => $this->from, 86 | 'total' => $this->total, 87 | ]; 88 | } 89 | 90 | public function count(): int 91 | { 92 | return \count($this->data); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Endpoints/Batches.php: -------------------------------------------------------------------------------- 1 | http->get(self::PATH.'/'.$batchUid); 16 | } 17 | 18 | public function all(array $query = []): array 19 | { 20 | return $this->http->get(self::PATH.'/', $query); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Endpoints/Delegates/HandlesBatches.php: -------------------------------------------------------------------------------- 1 | batches->get($uid); 18 | } 19 | 20 | public function getBatches(?BatchesQuery $options = null): BatchesResults 21 | { 22 | $query = null !== $options ? $options->toArray() : []; 23 | 24 | $response = $this->batches->all($query); 25 | 26 | return new BatchesResults($response); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Endpoints/Delegates/HandlesDocuments.php: -------------------------------------------------------------------------------- 1 | assertValidDocumentId($documentId); 18 | $query = isset($fields) ? ['fields' => implode(',', $fields)] : []; 19 | 20 | return $this->http->get(self::PATH.'/'.$this->uid.'/documents/'.$documentId, $query); 21 | } 22 | 23 | public function getDocuments(?DocumentsQuery $options = null): DocumentsResults 24 | { 25 | try { 26 | $options = $options ?? new DocumentsQuery(); 27 | $query = $options->toArray(); 28 | 29 | if ($options->hasFilter()) { 30 | $response = $this->http->post(self::PATH.'/'.$this->uid.'/documents/fetch', $query); 31 | } else { 32 | $response = $this->http->get(self::PATH.'/'.$this->uid.'/documents', $query); 33 | } 34 | 35 | return new DocumentsResults($response); 36 | } catch (\Exception $e) { 37 | throw ApiException::rethrowWithHint($e, __FUNCTION__); 38 | } 39 | } 40 | 41 | public function addDocuments(array $documents, ?string $primaryKey = null) 42 | { 43 | return $this->http->post(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey]); 44 | } 45 | 46 | public function addDocumentsJson(string $documents, ?string $primaryKey = null) 47 | { 48 | return $this->http->post(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey], 'application/json'); 49 | } 50 | 51 | public function addDocumentsCsv(string $documents, ?string $primaryKey = null, ?string $delimiter = null) 52 | { 53 | return $this->http->post(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey, 'csvDelimiter' => $delimiter], 'text/csv'); 54 | } 55 | 56 | public function addDocumentsNdjson(string $documents, ?string $primaryKey = null) 57 | { 58 | return $this->http->post(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey], 'application/x-ndjson'); 59 | } 60 | 61 | public function addDocumentsInBatches(array $documents, ?int $batchSize = 1000, ?string $primaryKey = null) 62 | { 63 | $promises = []; 64 | 65 | foreach (self::batch($documents, $batchSize) as $batch) { 66 | $promises[] = $this->addDocuments($batch, $primaryKey); 67 | } 68 | 69 | return $promises; 70 | } 71 | 72 | public function addDocumentsCsvInBatches(string $documents, ?int $batchSize = 1000, ?string $primaryKey = null, ?string $delimiter = null) 73 | { 74 | $promises = []; 75 | 76 | foreach (self::batchCsvString($documents, $batchSize) as $batch) { 77 | $promises[] = $this->addDocumentsCsv($batch, $primaryKey, $delimiter); 78 | } 79 | 80 | return $promises; 81 | } 82 | 83 | public function addDocumentsNdjsonInBatches(string $documents, ?int $batchSize = 1000, ?string $primaryKey = null) 84 | { 85 | $promises = []; 86 | 87 | foreach (self::batchNdjsonString($documents, $batchSize) as $batch) { 88 | $promises[] = $this->addDocumentsNdjson($batch, $primaryKey); 89 | } 90 | 91 | return $promises; 92 | } 93 | 94 | public function updateDocuments(array $documents, ?string $primaryKey = null) 95 | { 96 | return $this->http->put(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey]); 97 | } 98 | 99 | public function updateDocumentsJson(string $documents, ?string $primaryKey = null) 100 | { 101 | return $this->http->put(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey], 'application/json'); 102 | } 103 | 104 | public function updateDocumentsCsv(string $documents, ?string $primaryKey = null, ?string $delimiter = null) 105 | { 106 | return $this->http->put(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey, 'csvDelimiter' => $delimiter], 'text/csv'); 107 | } 108 | 109 | public function updateDocumentsNdjson(string $documents, ?string $primaryKey = null) 110 | { 111 | return $this->http->put(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey], 'application/x-ndjson'); 112 | } 113 | 114 | public function updateDocumentsInBatches(array $documents, ?int $batchSize = 1000, ?string $primaryKey = null) 115 | { 116 | $promises = []; 117 | 118 | foreach (self::batch($documents, $batchSize) as $batch) { 119 | $promises[] = $this->updateDocuments($batch, $primaryKey); 120 | } 121 | 122 | return $promises; 123 | } 124 | 125 | public function updateDocumentsCsvInBatches(string $documents, ?int $batchSize = 1000, ?string $primaryKey = null, ?string $delimiter = null) 126 | { 127 | $promises = []; 128 | 129 | foreach (self::batchCsvString($documents, $batchSize) as $batch) { 130 | $promises[] = $this->updateDocumentsCsv($batch, $primaryKey, $delimiter); 131 | } 132 | 133 | return $promises; 134 | } 135 | 136 | public function updateDocumentsNdjsonInBatches(string $documents, ?int $batchSize = 1000, ?string $primaryKey = null) 137 | { 138 | $promises = []; 139 | 140 | foreach (self::batchNdjsonString($documents, $batchSize) as $batch) { 141 | $promises[] = $this->updateDocumentsNdjson($batch, $primaryKey); 142 | } 143 | 144 | return $promises; 145 | } 146 | 147 | /** 148 | * This is an EXPERIMENTAL feature, which may break without a major version. 149 | * It's available after Meilisearch v1.10. 150 | * 151 | * More info about the feature: https://github.com/orgs/meilisearch/discussions/762 152 | * More info about experimental features in general: https://www.meilisearch.com/docs/reference/api/experimental-features 153 | * 154 | * @param non-empty-string $function 155 | * @param array{filter?: non-empty-string|list|null, context?: array} $options 156 | */ 157 | public function updateDocumentsByFunction(string $function, array $options = []) 158 | { 159 | return $this->http->post(self::PATH.'/'.$this->uid.'/documents/edit', array_merge(['function' => $function], $options)); 160 | } 161 | 162 | public function deleteAllDocuments(): array 163 | { 164 | return $this->http->delete(self::PATH.'/'.$this->uid.'/documents'); 165 | } 166 | 167 | public function deleteDocument($documentId): array 168 | { 169 | $this->assertValidDocumentId($documentId); 170 | 171 | return $this->http->delete(self::PATH.'/'.$this->uid.'/documents/'.$documentId); 172 | } 173 | 174 | public function deleteDocuments(array $options): array 175 | { 176 | try { 177 | if (\array_key_exists('filter', $options) && $options['filter']) { 178 | return $this->http->post(self::PATH.'/'.$this->uid.'/documents/delete', $options); 179 | } 180 | 181 | // backwards compatibility: 182 | // expect to be a array to send alongside as $documents_ids. 183 | return $this->http->post(self::PATH.'/'.$this->uid.'/documents/delete-batch', $options); 184 | } catch (InvalidResponseBodyException $e) { 185 | throw ApiException::rethrowWithHint($e, __FUNCTION__); 186 | } 187 | } 188 | 189 | private function assertValidDocumentId($documentId): void 190 | { 191 | if (!\is_string($documentId) && !\is_int($documentId)) { 192 | throw InvalidArgumentException::invalidType('documentId', ['string', 'int']); 193 | } 194 | 195 | if (\is_string($documentId) && '' === trim($documentId)) { 196 | throw InvalidArgumentException::emptyArgument('documentId'); 197 | } 198 | } 199 | 200 | private static function batchCsvString(string $documents, int $batchSize): \Generator 201 | { 202 | $parsedDocuments = preg_split("/\r\n|\n|\r/", trim($documents)); 203 | $csvHeader = $parsedDocuments[0]; 204 | array_shift($parsedDocuments); 205 | $batches = array_chunk($parsedDocuments, $batchSize); 206 | 207 | /** @var array $batch */ 208 | foreach ($batches as $batch) { 209 | array_unshift($batch, $csvHeader); 210 | $batch = implode("\n", $batch); 211 | 212 | yield $batch; 213 | } 214 | } 215 | 216 | private static function batchNdjsonString(string $documents, int $batchSize): \Generator 217 | { 218 | $parsedDocuments = preg_split("/\r\n|\n|\r/", trim($documents)); 219 | $batches = array_chunk($parsedDocuments, $batchSize); 220 | 221 | /** @var array $batch */ 222 | foreach ($batches as $batch) { 223 | $batch = implode("\n", $batch); 224 | 225 | yield $batch; 226 | } 227 | } 228 | 229 | private static function batch(array $documents, int $batchSize): \Generator 230 | { 231 | $batches = array_chunk($documents, $batchSize); 232 | 233 | foreach ($batches as $batch) { 234 | yield $batch; 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/Endpoints/Delegates/HandlesDumps.php: -------------------------------------------------------------------------------- 1 | dumps->create(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Endpoints/Delegates/HandlesIndex.php: -------------------------------------------------------------------------------- 1 | index->all($options ?? null); 18 | } 19 | 20 | public function getRawIndex(string $uid): array 21 | { 22 | return $this->index($uid)->fetchRawInfo(); 23 | } 24 | 25 | public function index(string $uid): Indexes 26 | { 27 | return new Indexes($this->http, $uid); 28 | } 29 | 30 | public function getIndex(string $uid): Indexes 31 | { 32 | return $this->index($uid)->fetchInfo(); 33 | } 34 | 35 | public function deleteIndex(string $uid): array 36 | { 37 | return $this->index($uid)->delete(); 38 | } 39 | 40 | public function createIndex(string $uid, array $options = []): array 41 | { 42 | return $this->index->create($uid, $options); 43 | } 44 | 45 | public function updateIndex(string $uid, array $options = []): array 46 | { 47 | return $this->index($uid)->update($options); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Endpoints/Delegates/HandlesKeys.php: -------------------------------------------------------------------------------- 1 | keys->all($options); 18 | } 19 | 20 | public function getRawKeys(): array 21 | { 22 | return $this->keys->allRaw(); 23 | } 24 | 25 | public function getKey($keyOrUid): Keys 26 | { 27 | return $this->keys->get($keyOrUid); 28 | } 29 | 30 | public function createKey(array $options = []): Keys 31 | { 32 | return $this->keys->create($options); 33 | } 34 | 35 | public function updateKey(string $keyOrUid, array $options = []): Keys 36 | { 37 | return $this->keys->update($keyOrUid, $options); 38 | } 39 | 40 | public function deleteKey(string $keyOrUid): array 41 | { 42 | return $this->keys->delete($keyOrUid); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Endpoints/Delegates/HandlesMultiSearch.php: -------------------------------------------------------------------------------- 1 | $queries 17 | */ 18 | public function multiSearch(array $queries = [], ?MultiSearchFederation $federation = null) 19 | { 20 | $body = []; 21 | 22 | foreach ($queries as $query) { 23 | $body[] = $query->toArray(); 24 | } 25 | 26 | $payload = ['queries' => $body]; 27 | if (null !== $federation) { 28 | $payload['federation'] = $federation->toArray(); 29 | } 30 | 31 | return $this->http->post('/multi-search', $payload); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Endpoints/Delegates/HandlesNetwork.php: -------------------------------------------------------------------------------- 1 | network->get(); 17 | 18 | return new NetworkResults($response); 19 | } 20 | 21 | /** 22 | * @param array{ 23 | * self?: non-empty-string, 24 | * remotes?: array 25 | * } $network 26 | */ 27 | public function updateNetwork(array $network): NetworkResults 28 | { 29 | $response = $this->network->update($network); 30 | 31 | return new NetworkResults($response); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Endpoints/Delegates/HandlesSettings.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | public function getRankingRules(): array 19 | { 20 | return $this->http->get(self::PATH.'/'.$this->uid.'/settings/ranking-rules'); 21 | } 22 | 23 | /** 24 | * @param list $rankingRules 25 | */ 26 | public function updateRankingRules(array $rankingRules): array 27 | { 28 | return $this->http->put(self::PATH.'/'.$this->uid.'/settings/ranking-rules', $rankingRules); 29 | } 30 | 31 | public function resetRankingRules(): array 32 | { 33 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/ranking-rules'); 34 | } 35 | 36 | // Settings - Distinct attribute 37 | 38 | /** 39 | * @return non-empty-string|null 40 | */ 41 | public function getDistinctAttribute(): ?string 42 | { 43 | return $this->http->get(self::PATH.'/'.$this->uid.'/settings/distinct-attribute'); 44 | } 45 | 46 | /** 47 | * @param non-empty-string $distinctAttribute 48 | */ 49 | public function updateDistinctAttribute(string $distinctAttribute): array 50 | { 51 | return $this->http->put(self::PATH.'/'.$this->uid.'/settings/distinct-attribute', $distinctAttribute); 52 | } 53 | 54 | public function resetDistinctAttribute(): array 55 | { 56 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/distinct-attribute'); 57 | } 58 | 59 | // Settings - Searchable attributes 60 | 61 | /** 62 | * @return list 63 | */ 64 | public function getSearchableAttributes(): array 65 | { 66 | return $this->http->get(self::PATH.'/'.$this->uid.'/settings/searchable-attributes'); 67 | } 68 | 69 | /** 70 | * @param list $searchableAttributes 71 | */ 72 | public function updateSearchableAttributes(array $searchableAttributes): array 73 | { 74 | return $this->http->put(self::PATH.'/'.$this->uid.'/settings/searchable-attributes', $searchableAttributes); 75 | } 76 | 77 | public function resetSearchableAttributes(): array 78 | { 79 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/searchable-attributes'); 80 | } 81 | 82 | // Settings - Displayed attributes 83 | 84 | /** 85 | * @return list 86 | */ 87 | public function getDisplayedAttributes(): array 88 | { 89 | return $this->http->get(self::PATH.'/'.$this->uid.'/settings/displayed-attributes'); 90 | } 91 | 92 | /** 93 | * @param list $displayedAttributes 94 | */ 95 | public function updateDisplayedAttributes(array $displayedAttributes): array 96 | { 97 | return $this->http->put(self::PATH.'/'.$this->uid.'/settings/displayed-attributes', $displayedAttributes); 98 | } 99 | 100 | public function resetDisplayedAttributes(): array 101 | { 102 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/displayed-attributes'); 103 | } 104 | 105 | // Settings - Localized attributes 106 | 107 | /** 108 | * @return list, locales: list}>|null 109 | */ 110 | public function getLocalizedAttributes(): ?array 111 | { 112 | return $this->http->get(self::PATH.'/'.$this->uid.'/settings/localized-attributes'); 113 | } 114 | 115 | /** 116 | * @param list, locales: list}> $localizedAttributes 117 | */ 118 | public function updateLocalizedAttributes(array $localizedAttributes): array 119 | { 120 | return $this->http->put(self::PATH.'/'.$this->uid.'/settings/localized-attributes', $localizedAttributes); 121 | } 122 | 123 | public function resetLocalizedAttributes(): array 124 | { 125 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/localized-attributes'); 126 | } 127 | 128 | // Settings - Faceting 129 | 130 | /** 131 | * @return array{maxValuesPerFacet: int, sortFacetValuesBy: array} 132 | */ 133 | public function getFaceting(): array 134 | { 135 | return (new Faceting($this->http->get(self::PATH.'/'.$this->uid.'/settings/faceting'))) 136 | ->getIterator()->getArrayCopy(); 137 | } 138 | 139 | /** 140 | * @param array{maxValuesPerFacet?: int, sortFacetValuesBy?: array} $faceting 141 | */ 142 | public function updateFaceting(array $faceting): array 143 | { 144 | return $this->http->patch(self::PATH.'/'.$this->uid.'/settings/faceting', $faceting); 145 | } 146 | 147 | public function resetFaceting(): array 148 | { 149 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/faceting'); 150 | } 151 | 152 | // Settings - Pagination 153 | 154 | /** 155 | * @return array{maxTotalHits: positive-int} 156 | */ 157 | public function getPagination(): array 158 | { 159 | return $this->http->get(self::PATH.'/'.$this->uid.'/settings/pagination'); 160 | } 161 | 162 | /** 163 | * @param array{maxTotalHits: positive-int} $pagination 164 | */ 165 | public function updatePagination(array $pagination): array 166 | { 167 | return $this->http->patch(self::PATH.'/'.$this->uid.'/settings/pagination', $pagination); 168 | } 169 | 170 | public function resetPagination(): array 171 | { 172 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/pagination'); 173 | } 174 | 175 | // Settings - Stop-words 176 | 177 | /** 178 | * @return list 179 | */ 180 | public function getStopWords(): array 181 | { 182 | return $this->http->get(self::PATH.'/'.$this->uid.'/settings/stop-words'); 183 | } 184 | 185 | /** 186 | * @param list $stopWords 187 | */ 188 | public function updateStopWords(array $stopWords): array 189 | { 190 | return $this->http->put(self::PATH.'/'.$this->uid.'/settings/stop-words', $stopWords); 191 | } 192 | 193 | public function resetStopWords(): array 194 | { 195 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/stop-words'); 196 | } 197 | 198 | // Settings - Synonyms 199 | 200 | /** 201 | * @return array> 202 | */ 203 | public function getSynonyms(): array 204 | { 205 | return (new Synonyms($this->http->get(self::PATH.'/'.$this->uid.'/settings/synonyms'))) 206 | ->getIterator()->getArrayCopy(); 207 | } 208 | 209 | /** 210 | * @param array> $synonyms 211 | */ 212 | public function updateSynonyms(array $synonyms): array 213 | { 214 | return $this->http->put(self::PATH.'/'.$this->uid.'/settings/synonyms', new Synonyms($synonyms)); 215 | } 216 | 217 | public function resetSynonyms(): array 218 | { 219 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/synonyms'); 220 | } 221 | 222 | // Settings - Filterable Attributes 223 | 224 | /** 225 | * @return list, 227 | * features?: array{ 228 | * facetSearch: bool, 229 | * filter: array{equality: bool, comparison: bool} 230 | * } 231 | * }> 232 | */ 233 | public function getFilterableAttributes(): array 234 | { 235 | return $this->http->get(self::PATH.'/'.$this->uid.'/settings/filterable-attributes'); 236 | } 237 | 238 | /** 239 | * @param list, 241 | * features?: array{facetSearch: bool, filter: array{equality: bool, comparison: bool}} 242 | * }> $filterableAttributes 243 | * 244 | * Note: When attributePatterns contains '_geo', the features field is ignored 245 | */ 246 | public function updateFilterableAttributes(array $filterableAttributes): array 247 | { 248 | return $this->http->put(self::PATH.'/'.$this->uid.'/settings/filterable-attributes', $filterableAttributes); 249 | } 250 | 251 | public function resetFilterableAttributes(): array 252 | { 253 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/filterable-attributes'); 254 | } 255 | 256 | // Settings - Sortable Attributes 257 | 258 | /** 259 | * @return list 260 | */ 261 | public function getSortableAttributes(): array 262 | { 263 | return $this->http->get(self::PATH.'/'.$this->uid.'/settings/sortable-attributes'); 264 | } 265 | 266 | /** 267 | * @param list $sortableAttributes 268 | */ 269 | public function updateSortableAttributes(array $sortableAttributes): array 270 | { 271 | return $this->http->put(self::PATH.'/'.$this->uid.'/settings/sortable-attributes', $sortableAttributes); 272 | } 273 | 274 | public function resetSortableAttributes(): array 275 | { 276 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/sortable-attributes'); 277 | } 278 | 279 | // Settings - Typo Tolerance 280 | 281 | /** 282 | * @return array{ 283 | * enabled: bool, 284 | * minWordSizeForTypos: array{oneTypo: int, twoTypos: int}, 285 | * disableOnWords: list, 286 | * disableOnAttributes: list 287 | * } 288 | */ 289 | public function getTypoTolerance(): array 290 | { 291 | return (new TypoTolerance($this->http->get(self::PATH.'/'.$this->uid.'/settings/typo-tolerance'))) 292 | ->getIterator()->getArrayCopy(); 293 | } 294 | 295 | /** 296 | * @param array{ 297 | * enabled: bool, 298 | * minWordSizeForTypos: array{oneTypo: int, twoTypos: int}, 299 | * disableOnWords: list, 300 | * disableOnAttributes: list 301 | * } $typoTolerance 302 | */ 303 | public function updateTypoTolerance(array $typoTolerance): array 304 | { 305 | return $this->http->patch(self::PATH.'/'.$this->uid.'/settings/typo-tolerance', new TypoTolerance($typoTolerance)); 306 | } 307 | 308 | public function resetTypoTolerance(): array 309 | { 310 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/typo-tolerance'); 311 | } 312 | 313 | // Settings - Word dictionary 314 | 315 | /** 316 | * @return list 317 | */ 318 | public function getDictionary(): array 319 | { 320 | return $this->http->get(self::PATH.'/'.$this->uid.'/settings/dictionary'); 321 | } 322 | 323 | /** 324 | * @param list $wordDictionary 325 | */ 326 | public function updateDictionary(array $wordDictionary): array 327 | { 328 | return $this->http->put(self::PATH.'/'.$this->uid.'/settings/dictionary', $wordDictionary); 329 | } 330 | 331 | public function resetDictionary(): array 332 | { 333 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/dictionary'); 334 | } 335 | 336 | // Settings - Separator tokens 337 | 338 | public function getSeparatorTokens(): array 339 | { 340 | return $this->http->get(self::PATH.'/'.$this->uid.'/settings/separator-tokens'); 341 | } 342 | 343 | /** 344 | * @param list $separatorTokens 345 | */ 346 | public function updateSeparatorTokens(array $separatorTokens): array 347 | { 348 | return $this->http->put(self::PATH.'/'.$this->uid.'/settings/separator-tokens', $separatorTokens); 349 | } 350 | 351 | public function resetSeparatorTokens(): array 352 | { 353 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/separator-tokens'); 354 | } 355 | 356 | // Settings - Non-Separator tokens 357 | 358 | /** 359 | * @return list 360 | */ 361 | public function getNonSeparatorTokens(): array 362 | { 363 | return $this->http->get(self::PATH.'/'.$this->uid.'/settings/non-separator-tokens'); 364 | } 365 | 366 | /** 367 | * @param list $nonSeparatorTokens 368 | */ 369 | public function updateNonSeparatorTokens(array $nonSeparatorTokens): array 370 | { 371 | return $this->http->put(self::PATH.'/'.$this->uid.'/settings/non-separator-tokens', $nonSeparatorTokens); 372 | } 373 | 374 | public function resetNonSeparatorTokens(): array 375 | { 376 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/non-separator-tokens'); 377 | } 378 | 379 | // Settings - proximityPrecision 380 | 381 | /** 382 | * @return 'byWord'|'byAttribute' 383 | */ 384 | public function getProximityPrecision(): string 385 | { 386 | return $this->http->get(self::PATH.'/'.$this->uid.'/settings/proximity-precision'); 387 | } 388 | 389 | /** 390 | * @param 'byWord'|'byAttribute' $type 391 | */ 392 | public function updateProximityPrecision(string $type): array 393 | { 394 | return $this->http->put(self::PATH.'/'.$this->uid.'/settings/proximity-precision', $type); 395 | } 396 | 397 | public function resetProximityPrecision(): array 398 | { 399 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/proximity-precision'); 400 | } 401 | 402 | // Settings - searchCutoffMs 403 | 404 | public function getSearchCutoffMs(): ?int 405 | { 406 | return $this->http->get(self::PATH.'/'.$this->uid.'/settings/search-cutoff-ms'); 407 | } 408 | 409 | public function updateSearchCutoffMs(int $value): array 410 | { 411 | return $this->http->put(self::PATH.'/'.$this->uid.'/settings/search-cutoff-ms', $value); 412 | } 413 | 414 | public function resetSearchCutoffMs(): array 415 | { 416 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/search-cutoff-ms'); 417 | } 418 | 419 | // Settings - Experimental: Embedders (hybrid search) 420 | 421 | public function getEmbedders(): ?array 422 | { 423 | return $this->http->get(self::PATH.'/'.$this->uid.'/settings/embedders'); 424 | } 425 | 426 | public function updateEmbedders(array $embedders): array 427 | { 428 | return $this->http->patch(self::PATH.'/'.$this->uid.'/settings/embedders', $embedders); 429 | } 430 | 431 | public function resetEmbedders(): array 432 | { 433 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/embedders'); 434 | } 435 | 436 | // Settings - Facet Search 437 | 438 | /** 439 | * @since Meilisearch v1.12.0 440 | */ 441 | public function getFacetSearch(): bool 442 | { 443 | return $this->http->get(self::PATH.'/'.$this->uid.'/settings/facet-search'); 444 | } 445 | 446 | /** 447 | * @since Meilisearch v1.12.0 448 | */ 449 | public function updateFacetSearch(bool $facetSearch): array 450 | { 451 | return $this->http->put(self::PATH.'/'.$this->uid.'/settings/facet-search', $facetSearch); 452 | } 453 | 454 | /** 455 | * @since Meilisearch v1.12.0 456 | */ 457 | public function resetFacetSearch(): array 458 | { 459 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/facet-search'); 460 | } 461 | 462 | // Settings - Prefix Search 463 | 464 | /** 465 | * @return 'indexingTime'|'disabled' 466 | * 467 | * @since Meilisearch v1.12.0 468 | */ 469 | public function getPrefixSearch(): string 470 | { 471 | return $this->http->get(self::PATH.'/'.$this->uid.'/settings/prefix-search'); 472 | } 473 | 474 | /** 475 | * @param 'indexingTime'|'disabled' $prefixSearch 476 | * 477 | * @since Meilisearch v1.12.0 478 | */ 479 | public function updatePrefixSearch(string $prefixSearch): array 480 | { 481 | return $this->http->put(self::PATH.'/'.$this->uid.'/settings/prefix-search', $prefixSearch); 482 | } 483 | 484 | /** 485 | * @since Meilisearch v1.12.0 486 | */ 487 | public function resetPrefixSearch(): array 488 | { 489 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/prefix-search'); 490 | } 491 | } 492 | -------------------------------------------------------------------------------- /src/Endpoints/Delegates/HandlesSnapshots.php: -------------------------------------------------------------------------------- 1 | snapshots->create(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Endpoints/Delegates/HandlesSystem.php: -------------------------------------------------------------------------------- 1 | health->show(); 22 | } 23 | 24 | public function isHealthy(): bool 25 | { 26 | try { 27 | $this->health->show(); 28 | } catch (\Exception $e) { 29 | return false; 30 | } 31 | 32 | return true; 33 | } 34 | 35 | public function version(): array 36 | { 37 | return $this->version->show(); 38 | } 39 | 40 | public function stats(): array 41 | { 42 | return $this->stats->show(); 43 | } 44 | 45 | public function generateTenantToken(string $apiKeyUid, $searchRules, array $options = []): string 46 | { 47 | return $this->tenantToken->generateTenantToken($apiKeyUid, $searchRules, $options); 48 | } 49 | 50 | public function swapIndexes(array $indexes): array 51 | { 52 | $options = array_map(static fn ($data) => ['indexes' => $data], $indexes); 53 | 54 | return $this->index->swapIndexes($options); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Endpoints/Delegates/HandlesTasks.php: -------------------------------------------------------------------------------- 1 | tasks->get($uid); 21 | } 22 | 23 | public function getTasks(?TasksQuery $options = null): TasksResults 24 | { 25 | $query = isset($options) ? $options->toArray() : []; 26 | 27 | $response = $this->tasks->all($query); 28 | 29 | return new TasksResults($response); 30 | } 31 | 32 | public function deleteTasks(?DeleteTasksQuery $options = null): array 33 | { 34 | return $this->tasks->deleteTasks($options); 35 | } 36 | 37 | public function cancelTasks(?CancelTasksQuery $options = null): array 38 | { 39 | return $this->tasks->cancelTasks($options); 40 | } 41 | 42 | /** 43 | * @throws TimeOutException 44 | */ 45 | public function waitForTask($uid, int $timeoutInMs = 5000, int $intervalInMs = 50): array 46 | { 47 | return $this->tasks->waitTask($uid, $timeoutInMs, $intervalInMs); 48 | } 49 | 50 | /** 51 | * @throws TimeOutException 52 | */ 53 | public function waitForTasks($uids, int $timeoutInMs = 5000, int $intervalInMs = 50): array 54 | { 55 | return $this->tasks->waitTasks($uids, $timeoutInMs, $intervalInMs); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Endpoints/Delegates/TasksQueryTrait.php: -------------------------------------------------------------------------------- 1 | types = $types; 26 | 27 | return $this; 28 | } 29 | 30 | /** 31 | * @return $this 32 | */ 33 | public function setStatuses(array $statuses): self 34 | { 35 | $this->statuses = $statuses; 36 | 37 | return $this; 38 | } 39 | 40 | /** 41 | * @return $this 42 | */ 43 | public function setIndexUids(array $indexUids): self 44 | { 45 | $this->indexUids = $indexUids; 46 | 47 | return $this; 48 | } 49 | 50 | public function getIndexUids(): array 51 | { 52 | return $this->indexUids ?? []; 53 | } 54 | 55 | /** 56 | * @return $this 57 | */ 58 | public function setUids(array $uids): self 59 | { 60 | $this->uids = $uids; 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * @return $this 67 | */ 68 | public function setBeforeEnqueuedAt(\DateTimeInterface $date): self 69 | { 70 | $this->beforeEnqueuedAt = $date; 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * @return $this 77 | */ 78 | public function setAfterEnqueuedAt(\DateTimeInterface $date): self 79 | { 80 | $this->afterEnqueuedAt = $date; 81 | 82 | return $this; 83 | } 84 | 85 | /** 86 | * @return $this 87 | */ 88 | public function setBeforeStartedAt(\DateTimeInterface $date): self 89 | { 90 | $this->beforeStartedAt = $date; 91 | 92 | return $this; 93 | } 94 | 95 | /** 96 | * @return $this 97 | */ 98 | public function setAfterStartedAt(\DateTimeInterface $date): self 99 | { 100 | $this->afterStartedAt = $date; 101 | 102 | return $this; 103 | } 104 | 105 | /** 106 | * @return $this 107 | */ 108 | public function setBeforeFinishedAt(\DateTimeInterface $date): self 109 | { 110 | $this->beforeFinishedAt = $date; 111 | 112 | return $this; 113 | } 114 | 115 | /** 116 | * @return $this 117 | */ 118 | public function setAfterFinishedAt(\DateTimeInterface $date): self 119 | { 120 | $this->afterFinishedAt = $date; 121 | 122 | return $this; 123 | } 124 | 125 | public function toArray(): array 126 | { 127 | return array_filter( 128 | $this->baseArray(), 129 | static function ($item) { return null !== $item; } 130 | ); 131 | } 132 | 133 | protected function baseArray(): array 134 | { 135 | return [ 136 | 'beforeEnqueuedAt' => $this->formatDate($this->beforeEnqueuedAt ?? null), 137 | 'afterEnqueuedAt' => $this->formatDate($this->afterEnqueuedAt ?? null), 138 | 'beforeStartedAt' => $this->formatDate($this->beforeStartedAt ?? null), 139 | 'afterStartedAt' => $this->formatDate($this->afterStartedAt ?? null), 140 | 'beforeFinishedAt' => $this->formatDate($this->beforeFinishedAt ?? null), 141 | 'afterFinishedAt' => $this->formatDate($this->afterFinishedAt ?? null), 142 | 'statuses' => $this->formatArray($this->statuses ?? null), 143 | 'uids' => $this->formatArray($this->uids ?? null), 144 | 'types' => $this->formatArray($this->types ?? null), 145 | 'indexUids' => $this->formatArray($this->indexUids ?? null), 146 | ]; 147 | } 148 | 149 | private function formatDate(?\DateTimeInterface $date): ?string 150 | { 151 | return null !== $date ? $date->format(\DateTimeInterface::RFC3339) : null; 152 | } 153 | 154 | private function formatArray(?array $array): ?string 155 | { 156 | return null !== $array ? implode(',', $array) : null; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/Endpoints/Dumps.php: -------------------------------------------------------------------------------- 1 | http->post(self::PATH); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Endpoints/Health.php: -------------------------------------------------------------------------------- 1 | uid = $uid; 40 | $this->primaryKey = $primaryKey; 41 | $this->createdAt = $createdAt; 42 | $this->updatedAt = $updatedAt; 43 | $this->tasks = new Tasks($http); 44 | 45 | parent::__construct($http); 46 | } 47 | 48 | protected function newInstance(array $attributes): self 49 | { 50 | return new self( 51 | $this->http, 52 | $attributes['uid'], 53 | $attributes['primaryKey'], 54 | static::parseDate($attributes['createdAt']), 55 | static::parseDate($attributes['updatedAt']), 56 | ); 57 | } 58 | 59 | /** 60 | * @return $this 61 | */ 62 | protected function fill(array $attributes): self 63 | { 64 | $this->uid = $attributes['uid']; 65 | $this->primaryKey = $attributes['primaryKey']; 66 | $this->createdAt = static::parseDate($attributes['createdAt']); 67 | $this->updatedAt = static::parseDate($attributes['updatedAt']); 68 | 69 | return $this; 70 | } 71 | 72 | /** 73 | * @throws \Exception|ApiException 74 | */ 75 | public function create(string $uid, array $options = []): array 76 | { 77 | $options['uid'] = $uid; 78 | 79 | return $this->http->post(self::PATH, $options); 80 | } 81 | 82 | public function all(?IndexesQuery $options = null): IndexesResults 83 | { 84 | $indexes = []; 85 | $query = isset($options) ? $options->toArray() : []; 86 | $response = $this->allRaw($query); 87 | 88 | foreach ($response['results'] as $index) { 89 | $indexes[] = $this->newInstance($index); 90 | } 91 | 92 | $response['results'] = $indexes; 93 | 94 | return new IndexesResults($response); 95 | } 96 | 97 | public function allRaw(array $options = []): array 98 | { 99 | return $this->http->get(self::PATH, $options); 100 | } 101 | 102 | public function getPrimaryKey(): ?string 103 | { 104 | return $this->primaryKey; 105 | } 106 | 107 | public function fetchPrimaryKey(): ?string 108 | { 109 | return $this->fetchInfo()->getPrimaryKey(); 110 | } 111 | 112 | public function getUid(): ?string 113 | { 114 | return $this->uid; 115 | } 116 | 117 | public function getCreatedAt(): ?\DateTimeInterface 118 | { 119 | return $this->createdAt; 120 | } 121 | 122 | public function getUpdatedAt(): ?\DateTimeInterface 123 | { 124 | return $this->updatedAt; 125 | } 126 | 127 | public function fetchRawInfo(): ?array 128 | { 129 | return $this->http->get(self::PATH.'/'.$this->uid); 130 | } 131 | 132 | public function fetchInfo(): self 133 | { 134 | $response = $this->fetchRawInfo(); 135 | 136 | return $this->fill($response); 137 | } 138 | 139 | public function update($body): array 140 | { 141 | return $this->http->patch(self::PATH.'/'.$this->uid, $body); 142 | } 143 | 144 | public function delete(): array 145 | { 146 | return $this->http->delete(self::PATH.'/'.$this->uid) ?? []; 147 | } 148 | 149 | /** 150 | * @param array $indexes 151 | */ 152 | public function swapIndexes(array $indexes): array 153 | { 154 | return $this->http->post('/swap-indexes', $indexes); 155 | } 156 | 157 | // Tasks 158 | 159 | public function getTask($uid): array 160 | { 161 | return $this->http->get('/tasks/'.$uid); 162 | } 163 | 164 | public function getTasks(?TasksQuery $options = null): TasksResults 165 | { 166 | $options = $options ?? new TasksQuery(); 167 | 168 | if (\count($options->getIndexUids()) > 0) { 169 | $options->setIndexUids(array_merge([$this->uid], $options->getIndexUids())); 170 | } else { 171 | $options->setIndexUids([$this->uid]); 172 | } 173 | 174 | $response = $this->http->get('/tasks', $options->toArray()); 175 | 176 | return new TasksResults($response); 177 | } 178 | 179 | // Search 180 | 181 | /** 182 | * @return SearchResult|array 183 | * 184 | * @phpstan-return ($options is array{raw: true|non-falsy-string|positive-int} ? array : SearchResult) 185 | */ 186 | public function search(?string $query, array $searchParams = [], array $options = []) 187 | { 188 | $result = $this->rawSearch($query, $searchParams); 189 | 190 | if (\array_key_exists('raw', $options) && $options['raw']) { 191 | return $result; 192 | } 193 | 194 | $searchResult = new SearchResult($result); 195 | $searchResult->applyOptions($options); 196 | 197 | return $searchResult; 198 | } 199 | 200 | public function rawSearch(?string $query, array $searchParams = []): array 201 | { 202 | $parameters = array_merge( 203 | ['q' => $query], 204 | $searchParams 205 | ); 206 | 207 | $result = $this->http->post(self::PATH.'/'.$this->uid.'/search', $parameters); 208 | 209 | // patch to prevent breaking in laravel/scout getTotalCount method, 210 | // affects only Meilisearch >= v0.28.0. 211 | if (isset($result['estimatedTotalHits'])) { 212 | $result['nbHits'] = $result['estimatedTotalHits']; 213 | } 214 | 215 | return $result; 216 | } 217 | 218 | public function searchSimilarDocuments(SimilarDocumentsQuery $parameters): SimilarDocumentsSearchResult 219 | { 220 | $result = $this->http->post(self::PATH.'/'.$this->uid.'/similar', $parameters->toArray()); 221 | 222 | return new SimilarDocumentsSearchResult($result); 223 | } 224 | 225 | // Facet Search 226 | 227 | public function facetSearch(FacetSearchQuery $params): FacetSearchResult 228 | { 229 | $response = $this->http->post(self::PATH.'/'.$this->uid.'/facet-search', $params->toArray()); 230 | 231 | return new FacetSearchResult($response); 232 | } 233 | 234 | // Stats 235 | 236 | public function stats(): array 237 | { 238 | return $this->http->get(self::PATH.'/'.$this->uid.'/stats'); 239 | } 240 | 241 | // Settings - Global 242 | 243 | public function getSettings(): array 244 | { 245 | return (new Settings($this->http->get(self::PATH.'/'.$this->uid.'/settings'))) 246 | ->getIterator()->getArrayCopy(); 247 | } 248 | 249 | public function updateSettings($settings): array 250 | { 251 | return $this->http->patch(self::PATH.'/'.$this->uid.'/settings', $settings); 252 | } 253 | 254 | public function resetSettings(): array 255 | { 256 | return $this->http->delete(self::PATH.'/'.$this->uid.'/settings'); 257 | } 258 | 259 | /** 260 | * @throws \Exception 261 | */ 262 | public static function parseDate(?string $dateTime): ?\DateTimeInterface 263 | { 264 | if (null === $dateTime) { 265 | return null; 266 | } 267 | 268 | try { 269 | return new \DateTimeImmutable($dateTime); 270 | } catch (\Exception $e) { 271 | // Trim 9th+ digit from fractional seconds. Meilisearch server can send 9 digits; PHP supports up to 8 272 | $trimPattern = '/(^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{1,8})(?:\d{1,})?(Z|[\+-]\d{2}:\d{2})$/'; 273 | $trimmedDate = preg_replace($trimPattern, '$1$2', $dateTime); 274 | 275 | return new \DateTimeImmutable($trimmedDate); 276 | } 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/Endpoints/Keys.php: -------------------------------------------------------------------------------- 1 | uid = $uid; 32 | $this->name = $name; 33 | $this->key = $key; 34 | $this->description = $description; 35 | $this->actions = $actions; 36 | $this->indexes = $indexes; 37 | $this->expiresAt = $expiresAt; 38 | $this->createdAt = $createdAt; 39 | $this->updatedAt = $updatedAt; 40 | 41 | parent::__construct($http); 42 | } 43 | 44 | protected function newInstance(array $attributes): self 45 | { 46 | $key = new self( 47 | $this->http, 48 | $attributes['uid'], 49 | $attributes['name'], 50 | $attributes['key'], 51 | $attributes['description'], 52 | $attributes['actions'], 53 | $attributes['indexes'], 54 | ); 55 | if ($attributes['expiresAt']) { 56 | $key->expiresAt = $this->createDate($attributes['expiresAt']); 57 | } 58 | if ($attributes['createdAt']) { 59 | $key->createdAt = $this->createDate($attributes['createdAt']); 60 | } 61 | if ($attributes['updatedAt']) { 62 | $key->updatedAt = $this->createDate($attributes['updatedAt']); 63 | } 64 | 65 | return $key; 66 | } 67 | 68 | /** 69 | * @return $this 70 | */ 71 | protected function fill(array $attributes): self 72 | { 73 | $this->uid = $attributes['uid']; 74 | $this->name = $attributes['name']; 75 | $this->key = $attributes['key']; 76 | $this->description = $attributes['description']; 77 | $this->actions = $attributes['actions']; 78 | $this->indexes = $attributes['indexes']; 79 | if ($attributes['expiresAt']) { 80 | $this->expiresAt = $this->createDate($attributes['expiresAt']); 81 | } 82 | if ($attributes['createdAt']) { 83 | $this->createdAt = $this->createDate($attributes['createdAt']); 84 | } 85 | if ($attributes['updatedAt']) { 86 | $this->updatedAt = $this->createDate($attributes['updatedAt']); 87 | } 88 | 89 | return $this; 90 | } 91 | 92 | protected function createDate($attribute): ?\DateTimeInterface 93 | { 94 | if (!\is_string($attribute)) { 95 | return null; 96 | } 97 | 98 | if (false === strpos($attribute, '.')) { 99 | $date = \DateTimeImmutable::createFromFormat(\DateTimeInterface::ATOM, $attribute); 100 | } else { 101 | $attribute = preg_replace('/(\.\d{6})\d+/', '$1', $attribute, 1); 102 | $date = \DateTimeImmutable::createFromFormat('Y-m-d\TH:i:s.uP', $attribute); 103 | } 104 | 105 | return false === $date ? null : $date; 106 | } 107 | 108 | public function getUid(): ?string 109 | { 110 | return $this->uid; 111 | } 112 | 113 | public function getName(): ?string 114 | { 115 | return $this->name; 116 | } 117 | 118 | public function getKey(): ?string 119 | { 120 | return $this->key; 121 | } 122 | 123 | public function getDescription(): ?string 124 | { 125 | return $this->description; 126 | } 127 | 128 | public function getActions(): ?array 129 | { 130 | return $this->actions; 131 | } 132 | 133 | public function getIndexes(): ?array 134 | { 135 | return $this->indexes; 136 | } 137 | 138 | public function getExpiresAt(): ?\DateTimeInterface 139 | { 140 | return $this->expiresAt; 141 | } 142 | 143 | public function getCreatedAt(): ?\DateTimeInterface 144 | { 145 | return $this->createdAt; 146 | } 147 | 148 | public function getUpdatedAt(): ?\DateTimeInterface 149 | { 150 | return $this->updatedAt; 151 | } 152 | 153 | public function get($keyOrUid): self 154 | { 155 | $response = $this->http->get(self::PATH.'/'.$keyOrUid); 156 | 157 | return $this->fill($response); 158 | } 159 | 160 | public function all(?KeysQuery $options = null): KeysResults 161 | { 162 | $query = isset($options) ? $options->toArray() : []; 163 | 164 | $keys = []; 165 | $response = $this->allRaw($query); 166 | 167 | foreach ($response['results'] as $key) { 168 | $keys[] = $this->newInstance($key); 169 | } 170 | 171 | $response['results'] = $keys; 172 | 173 | return new KeysResults($response); 174 | } 175 | 176 | public function allRaw(array $options = []): array 177 | { 178 | return $this->http->get(self::PATH.'/', $options); 179 | } 180 | 181 | public function create(array $options = []): self 182 | { 183 | if (isset($options['expiresAt']) && $options['expiresAt'] instanceof \DateTimeInterface) { 184 | $options['expiresAt'] = $options['expiresAt']->format('Y-m-d\TH:i:s.vu\Z'); 185 | } 186 | $response = $this->http->post(self::PATH, $options); 187 | 188 | return $this->fill($response); 189 | } 190 | 191 | public function update(string $keyOrUid, array $options = []): self 192 | { 193 | $data = array_intersect_key($options, array_flip(['description', 'name'])); 194 | $response = $this->http->patch(self::PATH.'/'.$keyOrUid, $data); 195 | 196 | return $this->fill($response); 197 | } 198 | 199 | public function delete(string $keyOrUid): array 200 | { 201 | return $this->http->delete(self::PATH.'/'.$keyOrUid) ?? []; 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/Endpoints/Network.php: -------------------------------------------------------------------------------- 1 | 17 | * } 18 | */ 19 | public function get(): array 20 | { 21 | return $this->http->get(self::PATH); 22 | } 23 | 24 | /** 25 | * @param array{ 26 | * self?: non-empty-string, 27 | * remotes?: array 28 | * } $body 29 | * 30 | * @return array{ 31 | * self: non-empty-string, 32 | * remotes: array 33 | * } 34 | */ 35 | public function update(array $body): array 36 | { 37 | return $this->http->patch(self::PATH, $body); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Endpoints/Snapshots.php: -------------------------------------------------------------------------------- 1 | http->post(self::PATH); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Endpoints/Stats.php: -------------------------------------------------------------------------------- 1 | http->get(self::PATH.'/'.$taskUid); 19 | } 20 | 21 | public function all(array $query = []): array 22 | { 23 | return $this->http->get(self::PATH.'/', $query); 24 | } 25 | 26 | public function cancelTasks(?CancelTasksQuery $options): array 27 | { 28 | $options = $options ?? new CancelTasksQuery(); 29 | 30 | return $this->http->post('/tasks/cancel', null, $options->toArray()); 31 | } 32 | 33 | public function deleteTasks(?DeleteTasksQuery $options): array 34 | { 35 | $options = $options ?? new DeleteTasksQuery(); 36 | 37 | return $this->http->delete(self::PATH, $options->toArray()); 38 | } 39 | 40 | /** 41 | * @throws TimeOutException 42 | */ 43 | public function waitTask($taskUid, int $timeoutInMs, int $intervalInMs): array 44 | { 45 | $timeoutTemp = 0; 46 | 47 | while ($timeoutInMs > $timeoutTemp) { 48 | $res = $this->get($taskUid); 49 | 50 | if ('enqueued' !== $res['status'] && 'processing' !== $res['status']) { 51 | return $res; 52 | } 53 | 54 | $timeoutTemp += $intervalInMs; 55 | usleep(1000 * $intervalInMs); 56 | } 57 | 58 | throw new TimeOutException(); 59 | } 60 | 61 | /** 62 | * @throws TimeOutException 63 | */ 64 | public function waitTasks(array $taskUids, int $timeoutInMs, int $intervalInMs): array 65 | { 66 | $tasks = []; 67 | 68 | foreach ($taskUids as $taskUid) { 69 | $tasks[] = $this->waitTask($taskUid, $timeoutInMs, $intervalInMs); 70 | } 71 | 72 | return $tasks; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Endpoints/TenantToken.php: -------------------------------------------------------------------------------- 1 | $options['expiresAt']) { 30 | throw InvalidArgumentException::dateIsExpired($options['expiresAt']); 31 | } 32 | } 33 | 34 | /** 35 | * Generate a new tenant token. 36 | * 37 | * The $options parameter is an array, and the following keys are accepted: 38 | * - apiKey: The API key parent of the token. If you leave it empty the client API Key will be used. 39 | * - expiresAt: A DateTime when the key will expire. Note that if an expiresAt value is included it should be in UTC time. 40 | * 41 | * @param array{apiKey?: ?string, expiresAt?: ?\DateTimeInterface} $options 42 | */ 43 | public function generateTenantToken(string $uid, $searchRules, array $options = []): string 44 | { 45 | if (!isset($options['apiKey']) || '' === $options['apiKey']) { 46 | $options['apiKey'] = $this->apiKey; 47 | } 48 | 49 | // Validate every field 50 | $this->validateTenantTokenArguments($searchRules, $options); 51 | 52 | $json = new Json(); 53 | 54 | // Standard JWT header for encryption with SHA256/HS256 algorithm 55 | $header = [ 56 | 'typ' => 'JWT', 57 | 'alg' => 'HS256', 58 | ]; 59 | 60 | // Add the required fields to the payload 61 | $payload = []; 62 | $payload['apiKeyUid'] = $uid; 63 | $payload['searchRules'] = $searchRules; 64 | if (isset($options['expiresAt'])) { 65 | $payload['exp'] = $options['expiresAt']->getTimestamp(); 66 | } 67 | 68 | // Serialize the Header 69 | $jsonHeader = $json->serialize($header); 70 | 71 | // Serialize the Payload 72 | $jsonPayload = $json->serialize($payload); 73 | 74 | // Encode Header to Base64Url String 75 | $encodedHeader = $this->base64url_encode($jsonHeader); 76 | 77 | // Encode Payload to Base64Url String 78 | $encodedPayload = $this->base64url_encode($jsonPayload); 79 | 80 | // Create Signature Hash 81 | $signature = hash_hmac('sha256', $encodedHeader.'.'.$encodedPayload, $options['apiKey'], true); 82 | 83 | // Encode Signature to Base64Url String 84 | $encodedSignature = $this->base64url_encode($signature); 85 | 86 | // Create JWT 87 | return $encodedHeader.'.'.$encodedPayload.'.'.$encodedSignature; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Endpoints/Version.php: -------------------------------------------------------------------------------- 1 | httpBody = $httpBody; 23 | $this->httpStatus = $response->getStatusCode(); 24 | $this->message = $this->getMessageFromHttpBody() ?? $response->getReasonPhrase(); 25 | $this->errorCode = $this->getErrorCodeFromHttpBody(); 26 | $this->errorLink = $this->getErrorLinkFromHttpBody(); 27 | $this->errorType = $this->getErrorTypeFromHttpBody(); 28 | 29 | parent::__construct($this->message, $this->httpStatus, $previous); 30 | } 31 | 32 | public function __toString() 33 | { 34 | $base = 'Meilisearch ApiException: Http Status: '.$this->httpStatus; 35 | 36 | if (!\is_null($this->message)) { 37 | $base .= ' - Message: '.$this->message; 38 | } 39 | 40 | if (!\is_null($this->errorCode)) { 41 | $base .= ' - Code: '.$this->errorCode; 42 | } 43 | 44 | if (!\is_null($this->errorType)) { 45 | $base .= ' - Type: '.$this->errorType; 46 | } 47 | 48 | if (!\is_null($this->errorLink)) { 49 | $base .= ' - Link: '.$this->errorLink; 50 | } 51 | 52 | return $base; 53 | } 54 | 55 | private function getMessageFromHttpBody(): ?string 56 | { 57 | if (\is_array($this->httpBody) && \array_key_exists('message', $this->httpBody)) { 58 | return $this->httpBody['message']; 59 | } 60 | 61 | return null; 62 | } 63 | 64 | private function getErrorCodeFromHttpBody(): ?string 65 | { 66 | if (\is_array($this->httpBody) && \array_key_exists('code', $this->httpBody)) { 67 | return $this->httpBody['code']; 68 | } 69 | 70 | return null; 71 | } 72 | 73 | private function getErrorTypeFromHttpBody(): ?string 74 | { 75 | if (\is_array($this->httpBody) && \array_key_exists('type', $this->httpBody)) { 76 | return $this->httpBody['type']; 77 | } 78 | 79 | return null; 80 | } 81 | 82 | private function getErrorLinkFromHttpBody(): ?string 83 | { 84 | if (\is_array($this->httpBody) && \array_key_exists('link', $this->httpBody)) { 85 | return $this->httpBody['link']; 86 | } 87 | 88 | return null; 89 | } 90 | 91 | public static function rethrowWithHint(\Exception $e, string $methodName) 92 | { 93 | return new \Exception(\sprintf(self::HINT_MESSAGE, $methodName), 0, $e); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Exceptions/CommunicationException.php: -------------------------------------------------------------------------------- 1 | getMessage(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Exceptions/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | format('Y-m-d H:i:s')), 31 | 400, 32 | null 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Exceptions/InvalidResponseBodyException.php: -------------------------------------------------------------------------------- 1 | httpStatus = $response->getStatusCode(); 18 | $this->httpBody = $httpBody; 19 | $this->message = $this->getMessageFromHttpBody() ?? $response->getReasonPhrase(); 20 | 21 | parent::__construct($this->message, $this->httpStatus, $previous); 22 | } 23 | 24 | public function __toString() 25 | { 26 | $base = 'Meilisearch InvalidResponseBodyException: Http Status: '.$this->httpStatus; 27 | 28 | if ($this->message) { 29 | $base .= ' - Message: '.$this->message; 30 | } 31 | 32 | return $base; 33 | } 34 | 35 | public function getMessageFromHttpBody(): ?string 36 | { 37 | if (null !== $this->httpBody) { 38 | $rawText = strip_tags($this->httpBody); 39 | 40 | if (!ctype_space($rawText)) { 41 | return substr(trim($rawText), 0, 100); 42 | } 43 | } 44 | 45 | return null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Exceptions/JsonDecodingException.php: -------------------------------------------------------------------------------- 1 | message = $message ?? 'Request timed out'; 15 | $this->code = $code ?? 408; 16 | 17 | parent::__construct($this->message, $this->code, $previous); 18 | } 19 | 20 | public function __toString() 21 | { 22 | $base = 'Meilisearch TimeOutException: Code: '.$this->code; 23 | if ($this->message) { 24 | return $base.' - Message: '.$this->message; 25 | } else { 26 | return $base; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Http/Client.php: -------------------------------------------------------------------------------- 1 | 32 | */ 33 | private array $headers; 34 | /** 35 | * @var non-empty-string 36 | */ 37 | private string $baseUrl; 38 | private Json $json; 39 | 40 | /** 41 | * @param non-empty-string $url 42 | * @param array $clientAgents 43 | */ 44 | public function __construct( 45 | string $url, 46 | ?string $apiKey = null, 47 | ?ClientInterface $httpClient = null, 48 | ?RequestFactoryInterface $reqFactory = null, 49 | array $clientAgents = [], 50 | ?StreamFactoryInterface $streamFactory = null 51 | ) { 52 | $this->baseUrl = $url; 53 | $this->http = $httpClient ?? Psr18ClientDiscovery::find(); 54 | $this->requestFactory = $reqFactory ?? Psr17FactoryDiscovery::findRequestFactory(); 55 | $this->streamFactory = $streamFactory ?? Psr17FactoryDiscovery::findStreamFactory(); 56 | $this->headers = [ 57 | 'User-Agent' => implode(';', array_merge($clientAgents, [Meilisearch::qualifiedVersion()])), 58 | ]; 59 | if (null !== $apiKey && '' !== $apiKey) { 60 | $this->headers['Authorization'] = \sprintf('Bearer %s', $apiKey); 61 | } 62 | $this->json = new Json(); 63 | } 64 | 65 | /** 66 | * @throws ClientExceptionInterface 67 | * @throws ApiException 68 | * @throws CommunicationException 69 | */ 70 | public function get(string $path, array $query = []) 71 | { 72 | $request = $this->requestFactory->createRequest( 73 | 'GET', 74 | $this->baseUrl.$path.$this->buildQueryString($query) 75 | ); 76 | 77 | return $this->execute($request); 78 | } 79 | 80 | /** 81 | * @param non-empty-string|null $contentType 82 | * 83 | * @throws ApiException 84 | * @throws ClientExceptionInterface 85 | * @throws CommunicationException 86 | * @throws JsonEncodingException 87 | */ 88 | public function post(string $path, $body = null, array $query = [], ?string $contentType = null) 89 | { 90 | if (null === $contentType) { 91 | $body = $this->json->serialize($body); 92 | } 93 | $request = $this->requestFactory->createRequest( 94 | 'POST', 95 | $this->baseUrl.$path.$this->buildQueryString($query) 96 | )->withBody($this->streamFactory->createStream($body)); 97 | 98 | return $this->execute($request, ['Content-type' => $contentType ?? 'application/json']); 99 | } 100 | 101 | /** 102 | * @param non-empty-string|null $contentType 103 | * 104 | * @throws ApiException 105 | * @throws ClientExceptionInterface 106 | * @throws CommunicationException 107 | * @throws JsonEncodingException 108 | */ 109 | public function put(string $path, $body = null, array $query = [], ?string $contentType = null) 110 | { 111 | if (null === $contentType) { 112 | $body = $this->json->serialize($body); 113 | } 114 | $request = $this->requestFactory->createRequest( 115 | 'PUT', 116 | $this->baseUrl.$path.$this->buildQueryString($query) 117 | )->withBody($this->streamFactory->createStream($body)); 118 | 119 | return $this->execute($request, ['Content-type' => $contentType ?? 'application/json']); 120 | } 121 | 122 | public function patch(string $path, $body = null, array $query = []) 123 | { 124 | $request = $this->requestFactory->createRequest( 125 | 'PATCH', 126 | $this->baseUrl.$path.$this->buildQueryString($query) 127 | )->withBody($this->streamFactory->createStream($this->json->serialize($body))); 128 | 129 | return $this->execute($request, ['Content-type' => 'application/json']); 130 | } 131 | 132 | public function delete(string $path, array $query = []) 133 | { 134 | $request = $this->requestFactory->createRequest( 135 | 'DELETE', 136 | $this->baseUrl.$path.$this->buildQueryString($query) 137 | ); 138 | 139 | return $this->execute($request); 140 | } 141 | 142 | /** 143 | * @param array $headers 144 | * 145 | * @throws ApiException 146 | * @throws ClientExceptionInterface 147 | * @throws CommunicationException 148 | */ 149 | private function execute(RequestInterface $request, array $headers = []) 150 | { 151 | foreach (array_merge($this->headers, $headers) as $header => $value) { 152 | $request = $request->withAddedHeader($header, $value); 153 | } 154 | 155 | try { 156 | return $this->parseResponse($this->http->sendRequest($request)); 157 | } catch (NetworkExceptionInterface $e) { 158 | throw new CommunicationException($e->getMessage(), $e->getCode(), $e); 159 | } 160 | } 161 | 162 | private function buildQueryString(array $queryParams = []): string 163 | { 164 | return \count($queryParams) > 0 ? '?'.http_build_query($queryParams) : ''; 165 | } 166 | 167 | /** 168 | * @throws ApiException 169 | * @throws InvalidResponseBodyException 170 | * @throws JsonDecodingException 171 | */ 172 | private function parseResponse(ResponseInterface $response) 173 | { 174 | if (204 === $response->getStatusCode()) { 175 | return null; 176 | } 177 | 178 | if (!$this->isJSONResponse($response->getHeader('content-type'))) { 179 | throw new InvalidResponseBodyException($response, (string) $response->getBody()); 180 | } 181 | 182 | if ($response->getStatusCode() >= 300) { 183 | $body = $this->json->unserialize((string) $response->getBody()) ?? $response->getReasonPhrase(); 184 | 185 | throw new ApiException($response, $body); 186 | } 187 | 188 | return $this->json->unserialize((string) $response->getBody()); 189 | } 190 | 191 | /** 192 | * Checks if any of the header values indicate a JSON response. 193 | * 194 | * @param array $headerValues the array of header values to check 195 | * 196 | * @return bool true if any header value contains 'application/json', otherwise false 197 | */ 198 | private function isJSONResponse(array $headerValues): bool 199 | { 200 | $filteredHeaders = array_filter($headerValues, static function (string $headerValue) { 201 | return false !== strpos($headerValue, 'application/json'); 202 | }); 203 | 204 | return \count($filteredHeaders) > 0; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/Http/Serialize/Json.php: -------------------------------------------------------------------------------- 1 | getMessage()), $e->getCode(), $e); 21 | } 22 | 23 | return $encoded; 24 | } 25 | 26 | public function unserialize(string $string) 27 | { 28 | try { 29 | $decoded = json_decode($string, true, 512, JSON_THROW_ON_ERROR); 30 | } catch (\JsonException $e) { 31 | throw new JsonDecodingException(\sprintf(self::JSON_DECODE_ERROR_MESSAGE, $e->getMessage()), $e->getCode(), $e); 32 | } 33 | 34 | return $decoded; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Http/Serialize/SerializerInterface.php: -------------------------------------------------------------------------------- 1 | |null $data 16 | * 17 | * @return string|bool 18 | * 19 | * @throws JsonEncodingException 20 | */ 21 | public function serialize($data); 22 | 23 | /** 24 | * Unserialize the given string. 25 | * 26 | * @return string|int|float|bool|array|null 27 | * 28 | * @throws JsonDecodingException 29 | */ 30 | public function unserialize(string $string); 31 | } 32 | -------------------------------------------------------------------------------- /src/Meilisearch.php: -------------------------------------------------------------------------------- 1 | >> 9 | */ 10 | class FacetSearchResult implements \Countable, \IteratorAggregate 11 | { 12 | /** 13 | * @var array> 14 | */ 15 | private array $facetHits; 16 | private int $processingTimeMs; 17 | private ?string $facetQuery; 18 | 19 | public function __construct(array $body) 20 | { 21 | $this->facetHits = $body['facetHits'] ?? []; 22 | $this->facetQuery = $body['facetQuery']; 23 | $this->processingTimeMs = $body['processingTimeMs']; 24 | } 25 | 26 | /** 27 | * @return array 28 | */ 29 | public function getFacetHits(): array 30 | { 31 | return $this->facetHits; 32 | } 33 | 34 | public function getProcessingTimeMs(): int 35 | { 36 | return $this->processingTimeMs; 37 | } 38 | 39 | public function toArray(): array 40 | { 41 | return [ 42 | 'facetHits' => $this->facetHits, 43 | 'facetQuery' => $this->facetQuery, 44 | 'processingTimeMs' => $this->processingTimeMs, 45 | ]; 46 | } 47 | 48 | public function toJSON(): string 49 | { 50 | return json_encode($this->toArray(), JSON_PRETTY_PRINT); 51 | } 52 | 53 | public function getIterator(): \ArrayIterator 54 | { 55 | return new \ArrayIterator($this->facetHits); 56 | } 57 | 58 | public function count(): int 59 | { 60 | return \count($this->facetHits); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Search/SearchResult.php: -------------------------------------------------------------------------------- 1 | >> 9 | */ 10 | class SearchResult implements \Countable, \IteratorAggregate 11 | { 12 | /** 13 | * @var array> 14 | */ 15 | private array $hits; 16 | 17 | /** 18 | * `estimatedTotalHits` is the attributes returned by the Meilisearch server 19 | * and its value will not be modified by the methods in this class. 20 | * Please, use `hitsCount` if you want to know the real size of the `hits` array at any time. 21 | */ 22 | private ?int $estimatedTotalHits = null; 23 | private int $hitsCount; 24 | private ?int $offset = null; 25 | private ?int $limit = null; 26 | private int $semanticHitCount; 27 | 28 | private ?int $hitsPerPage = null; 29 | private ?int $page = null; 30 | private ?int $totalPages = null; 31 | private ?int $totalHits = null; 32 | 33 | private int $processingTimeMs; 34 | private bool $numberedPagination; 35 | 36 | private string $query; 37 | 38 | /** 39 | * @var array 40 | */ 41 | private array $facetDistribution; 42 | 43 | /** 44 | * @var array 45 | */ 46 | private array $facetStats; 47 | 48 | /** 49 | * @var array 50 | */ 51 | private array $raw; 52 | 53 | public function __construct(array $body) 54 | { 55 | if (isset($body['estimatedTotalHits'])) { 56 | $this->numberedPagination = false; 57 | 58 | $this->offset = $body['offset']; 59 | $this->limit = $body['limit']; 60 | $this->estimatedTotalHits = $body['estimatedTotalHits']; 61 | } else { 62 | $this->numberedPagination = true; 63 | 64 | $this->hitsPerPage = $body['hitsPerPage']; 65 | $this->page = $body['page']; 66 | $this->totalPages = $body['totalPages']; 67 | $this->totalHits = $body['totalHits']; 68 | } 69 | 70 | $this->semanticHitCount = $body['semanticHitCount'] ?? 0; 71 | $this->hits = $body['hits'] ?? []; 72 | $this->hitsCount = \count($body['hits']); 73 | $this->processingTimeMs = $body['processingTimeMs']; 74 | $this->query = $body['query']; 75 | $this->facetDistribution = $body['facetDistribution'] ?? []; 76 | $this->facetStats = $body['facetStats'] ?? []; 77 | $this->raw = $body; 78 | } 79 | 80 | /** 81 | * Return a new {@see SearchResult} instance. 82 | * 83 | * The $options parameter is an array, and the following keys are accepted: 84 | * - transformFacetDistribution (callable) 85 | * - transformHits (callable) 86 | * 87 | * The method does NOT trigger a new search. 88 | */ 89 | public function applyOptions($options): self 90 | { 91 | if (\array_key_exists('transformHits', $options) && \is_callable($options['transformHits'])) { 92 | $this->transformHits($options['transformHits']); 93 | } 94 | if (\array_key_exists('transformFacetDistribution', $options) && \is_callable($options['transformFacetDistribution'])) { 95 | $this->transformFacetDistribution($options['transformFacetDistribution']); 96 | } 97 | 98 | return $this; 99 | } 100 | 101 | public function transformHits(callable $callback): self 102 | { 103 | $this->hits = $callback($this->hits); 104 | $this->hitsCount = \count($this->hits); 105 | 106 | return $this; 107 | } 108 | 109 | public function transformFacetDistribution(callable $callback): self 110 | { 111 | $this->facetDistribution = $callback($this->facetDistribution); 112 | 113 | return $this; 114 | } 115 | 116 | public function getHit(int $key, $default = null) 117 | { 118 | return $this->hits[$key] ?? $default; 119 | } 120 | 121 | /** 122 | * @return array 123 | */ 124 | public function getHits(): array 125 | { 126 | return $this->hits; 127 | } 128 | 129 | public function getOffset(): ?int 130 | { 131 | return $this->offset; 132 | } 133 | 134 | public function getLimit(): ?int 135 | { 136 | return $this->limit; 137 | } 138 | 139 | public function getHitsCount(): int 140 | { 141 | return $this->hitsCount; 142 | } 143 | 144 | /** 145 | * @return non-negative-int 146 | */ 147 | public function getSemanticHitCount(): int 148 | { 149 | return $this->semanticHitCount; 150 | } 151 | 152 | public function count(): int 153 | { 154 | return $this->hitsCount; 155 | } 156 | 157 | public function getEstimatedTotalHits(): ?int 158 | { 159 | return $this->estimatedTotalHits; 160 | } 161 | 162 | public function getProcessingTimeMs(): int 163 | { 164 | return $this->processingTimeMs; 165 | } 166 | 167 | public function getQuery(): string 168 | { 169 | return $this->query; 170 | } 171 | 172 | public function getHitsPerPage(): ?int 173 | { 174 | return $this->hitsPerPage; 175 | } 176 | 177 | public function getPage(): ?int 178 | { 179 | return $this->page; 180 | } 181 | 182 | public function getTotalPages(): ?int 183 | { 184 | return $this->totalPages; 185 | } 186 | 187 | public function getTotalHits(): ?int 188 | { 189 | return $this->totalHits; 190 | } 191 | 192 | /** 193 | * @return array 194 | */ 195 | public function getFacetDistribution(): array 196 | { 197 | return $this->facetDistribution; 198 | } 199 | 200 | /** 201 | * @return array 202 | */ 203 | public function getFacetStats(): array 204 | { 205 | return $this->facetStats; 206 | } 207 | 208 | /** 209 | * Return the original search result. 210 | * 211 | * @return array 212 | */ 213 | public function getRaw(): array 214 | { 215 | return $this->raw; 216 | } 217 | 218 | public function toArray(): array 219 | { 220 | $arr = [ 221 | 'hits' => $this->hits, 222 | 'hitsCount' => $this->hitsCount, 223 | 'processingTimeMs' => $this->processingTimeMs, 224 | 'query' => $this->query, 225 | 'facetDistribution' => $this->facetDistribution, 226 | ]; 227 | 228 | if (!$this->numberedPagination) { 229 | $arr = array_merge($arr, [ 230 | 'offset' => $this->offset, 231 | 'limit' => $this->limit, 232 | 'estimatedTotalHits' => $this->estimatedTotalHits, 233 | ]); 234 | } else { 235 | $arr = array_merge($arr, [ 236 | 'hitsPerPage' => $this->hitsPerPage, 237 | 'page' => $this->page, 238 | 'totalPages' => $this->totalPages, 239 | 'totalHits' => $this->totalHits, 240 | ]); 241 | } 242 | 243 | return $arr; 244 | } 245 | 246 | public function toJSON(): string 247 | { 248 | return json_encode($this->toArray(), JSON_PRETTY_PRINT); 249 | } 250 | 251 | public function getIterator(): \ArrayIterator 252 | { 253 | return new \ArrayIterator($this->hits); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/Search/SimilarDocumentsSearchResult.php: -------------------------------------------------------------------------------- 1 | >> 9 | */ 10 | class SimilarDocumentsSearchResult implements \Countable, \IteratorAggregate 11 | { 12 | /** 13 | * @var array> 14 | */ 15 | private array $hits; 16 | 17 | /** 18 | * `estimatedTotalHits` is the attributes returned by the Meilisearch server 19 | * and its value will not be modified by the methods in this class. 20 | * Please, use `hitsCount` if you want to know the real size of the `hits` array at any time. 21 | */ 22 | private int $estimatedTotalHits; 23 | private int $hitsCount; 24 | private int $offset; 25 | private int $limit; 26 | private int $processingTimeMs; 27 | private string $id; 28 | 29 | public function __construct(array $body) 30 | { 31 | $this->id = $body['id']; 32 | $this->hits = $body['hits']; 33 | $this->hitsCount = \count($body['hits']); 34 | $this->processingTimeMs = $body['processingTimeMs']; 35 | $this->offset = $body['offset']; 36 | $this->limit = $body['limit']; 37 | $this->estimatedTotalHits = $body['estimatedTotalHits']; 38 | } 39 | 40 | /** 41 | * @return array|null 42 | */ 43 | public function getHit(int $key): ?array 44 | { 45 | return $this->hits[$key]; 46 | } 47 | 48 | /** 49 | * @return array> 50 | */ 51 | public function getHits(): array 52 | { 53 | return $this->hits; 54 | } 55 | 56 | public function getOffset(): int 57 | { 58 | return $this->offset; 59 | } 60 | 61 | public function getLimit(): int 62 | { 63 | return $this->limit; 64 | } 65 | 66 | public function getEstimatedTotalHits(): int 67 | { 68 | return $this->estimatedTotalHits; 69 | } 70 | 71 | public function getProcessingTimeMs(): int 72 | { 73 | return $this->processingTimeMs; 74 | } 75 | 76 | public function getId(): string 77 | { 78 | return $this->id; 79 | } 80 | 81 | public function getHitsCount(): int 82 | { 83 | return $this->hitsCount; 84 | } 85 | 86 | /** 87 | * Converts the SimilarDocumentsSearchResult to an array representation. 88 | * 89 | * @return array 90 | */ 91 | public function toArray(): array 92 | { 93 | return [ 94 | 'id' => $this->id, 95 | 'hits' => $this->hits, 96 | 'hitsCount' => $this->hitsCount, 97 | 'processingTimeMs' => $this->processingTimeMs, 98 | 'offset' => $this->offset, 99 | 'limit' => $this->limit, 100 | 'estimatedTotalHits' => $this->estimatedTotalHits, 101 | ]; 102 | } 103 | 104 | public function getIterator(): \ArrayIterator 105 | { 106 | return new \ArrayIterator($this->hits); 107 | } 108 | 109 | public function count(): int 110 | { 111 | return $this->hitsCount; 112 | } 113 | } 114 | --------------------------------------------------------------------------------