├── .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 |
3 |
4 |
5 | Meilisearch PHP
6 |
7 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
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 |
--------------------------------------------------------------------------------