├── CNAME ├── src ├── Exceptions │ ├── ExceptionInterface.php │ ├── LogicException.php │ ├── CommunicationException.php │ ├── TimeOutException.php │ ├── InvalidArgumentException.php │ ├── InvalidResponseBodyException.php │ └── ApiException.php ├── Endpoints │ ├── Stats.php │ ├── Health.php │ ├── Version.php │ ├── Delegates │ │ ├── HandlesDumps.php │ │ ├── HandlesSnapshots.php │ │ ├── HandlesBatches.php │ │ ├── HandlesChatWorkspaces.php │ │ ├── HandlesMultiSearch.php │ │ ├── HandlesNetwork.php │ │ ├── HandlesKeys.php │ │ ├── HandlesTasks.php │ │ ├── HandlesIndex.php │ │ ├── HandlesSystem.php │ │ ├── HandlesChatWorkspaceSettings.php │ │ ├── TasksQueryTrait.php │ │ └── HandlesDocuments.php │ ├── Dumps.php │ ├── Snapshots.php │ ├── Batches.php │ ├── Network.php │ ├── ChatWorkspaces.php │ ├── Tasks.php │ ├── TenantToken.php │ ├── Keys.php │ └── Indexes.php ├── Contracts │ ├── CancelTasksQuery.php │ ├── TaskDetails.php │ ├── Index │ │ ├── Faceting.php │ │ ├── Synonyms.php │ │ ├── Embedders.php │ │ ├── TypoTolerance.php │ │ └── Settings.php │ ├── TaskStatus.php │ ├── Endpoint.php │ ├── TaskDetails │ │ ├── UnknownTaskDetails.php │ │ ├── DumpCreationDetails.php │ │ ├── IndexSwapDetails.php │ │ ├── IndexUpdateDetails.php │ │ ├── IndexCreationDetails.php │ │ ├── IndexDeletionDetails.php │ │ ├── DocumentAdditionOrUpdateDetails.php │ │ ├── DocumentEditionDetails.php │ │ ├── DocumentDeletionDetails.php │ │ ├── TaskDeletionDetails.php │ │ ├── TaskCancelationDetails.php │ │ └── SettingsUpdateDetails.php │ ├── TaskType.php │ ├── DeleteTasksQuery.php │ ├── TaskError.php │ ├── Data.php │ ├── FederationOptions.php │ ├── HybridSearchOptions.php │ ├── KeysQuery.php │ ├── IndexesQuery.php │ ├── KeysResults.php │ ├── DocumentsResults.php │ ├── NetworkResults.php │ ├── IndexesResults.php │ ├── Http.php │ ├── Version.php │ ├── BatchesQuery.php │ ├── Stats.php │ ├── ChatWorkspacesResults.php │ ├── BatchesResults.php │ ├── ChatWorkspacePromptsSettings.php │ ├── TasksResults.php │ ├── TasksQuery.php │ ├── MultiSearchFederation.php │ ├── IndexStats.php │ ├── FacetSearchQuery.php │ ├── DocumentsQuery.php │ ├── ChatWorkspaceSettings.php │ ├── SimilarDocumentsQuery.php │ ├── Task.php │ └── SearchQuery.php ├── Meilisearch.php ├── Http │ ├── Serialize │ │ ├── Json.php │ │ └── SerializerInterface.php │ └── Client.php ├── functions.php ├── Search │ ├── FacetSearchResult.php │ ├── SimilarDocumentsSearchResult.php │ └── SearchResult.php └── Client.php ├── .yamllint.yml ├── Dockerfile ├── .phpdoc └── template │ └── base.html.twig ├── phpstan.dist.neon ├── docker-compose.yml ├── phpunit.xml.dist.bak ├── LICENSE ├── composer.json └── README.md /CNAME: -------------------------------------------------------------------------------- 1 | php-sdk-docs.meilisearch.com 2 | -------------------------------------------------------------------------------- /src/Exceptions/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | /usr/local/etc/php/conf.d/memory-limit.ini 9 | 10 | COPY --from=composer:2 /usr/bin/composer /usr/local/bin/composer 11 | -------------------------------------------------------------------------------- /src/Contracts/TaskDetails.php: -------------------------------------------------------------------------------- 1 | getIterator()->getArrayCopy(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Contracts/Index/Synonyms.php: -------------------------------------------------------------------------------- 1 | getIterator()->getArrayCopy(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Contracts/TaskStatus.php: -------------------------------------------------------------------------------- 1 | getIterator()->getArrayCopy(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Contracts/Index/TypoTolerance.php: -------------------------------------------------------------------------------- 1 | getIterator()->getArrayCopy(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Exceptions/CommunicationException.php: -------------------------------------------------------------------------------- 1 | getMessage(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Meilisearch.php: -------------------------------------------------------------------------------- 1 | 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /src/Endpoints/Delegates/HandlesDumps.php: -------------------------------------------------------------------------------- 1 | dumps->create(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Endpoints/Delegates/HandlesSnapshots.php: -------------------------------------------------------------------------------- 1 | snapshots->create(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/Http/Serialize/Json.php: -------------------------------------------------------------------------------- 1 | http->post(self::PATH), partial(Tasks::waitTask(...), $this->http)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Endpoints/Snapshots.php: -------------------------------------------------------------------------------- 1 | http->post(self::PATH), partial(Tasks::waitTask(...), $this->http)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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-enterprise:latest 18 | ports: 19 | - "7700" 20 | environment: 21 | - MEILI_MASTER_KEY=masterKey 22 | - MEILI_NO_ANALYTICS=true 23 | -------------------------------------------------------------------------------- /src/Contracts/TaskDetails/UnknownTaskDetails.php: -------------------------------------------------------------------------------- 1 | > 11 | */ 12 | final class UnknownTaskDetails implements TaskDetails 13 | { 14 | /** 15 | * @param array $data 16 | */ 17 | public function __construct( 18 | public readonly array $data, 19 | ) { 20 | } 21 | 22 | public static function fromArray(array $data): self 23 | { 24 | return new self($data); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Http/Serialize/SerializerInterface.php: -------------------------------------------------------------------------------- 1 | |null $data 13 | * 14 | * @throws \JsonException 15 | */ 16 | public function serialize(mixed $data): string; 17 | 18 | /** 19 | * Unserialize the given string. 20 | * 21 | * @return string|int|float|bool|array|null 22 | * 23 | * @throws \JsonException 24 | */ 25 | public function unserialize(string $string): mixed; 26 | } 27 | -------------------------------------------------------------------------------- /src/Contracts/TaskDetails/DumpCreationDetails.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class DumpCreationDetails implements TaskDetails 15 | { 16 | /** 17 | * @param non-empty-string|null $dumpUid 18 | */ 19 | public function __construct( 20 | public readonly ?string $dumpUid, 21 | ) { 22 | } 23 | 24 | public static function fromArray(array $data): self 25 | { 26 | return new self( 27 | $data['dumpUid'], 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Exceptions/TimeOutException.php: -------------------------------------------------------------------------------- 1 | code; 17 | if ('' !== $this->message) { 18 | return $base.' - Message: '.$this->message; 19 | } 20 | 21 | return $base; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Contracts/TaskDetails/IndexSwapDetails.php: -------------------------------------------------------------------------------- 1 | 12 | * }> 13 | */ 14 | final class IndexSwapDetails implements TaskDetails 15 | { 16 | /** 17 | * @param array $swaps 18 | */ 19 | public function __construct( 20 | public readonly array $swaps, 21 | ) { 22 | } 23 | 24 | public static function fromArray(array $data): self 25 | { 26 | return new self( 27 | $data['swaps'], 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /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/Contracts/TaskType.php: -------------------------------------------------------------------------------- 1 | chats->listWorkspaces(); 20 | } 21 | 22 | /** 23 | * Get a specific chat workspace instance. 24 | */ 25 | public function chatWorkspace(string $workspaceName): ChatWorkspaces 26 | { 27 | return $this->chats->workspace($workspaceName); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Exceptions/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | format('Y-m-d H:i:s')), 22 | 400, 23 | null 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Contracts/TaskDetails/IndexUpdateDetails.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class IndexUpdateDetails implements TaskDetails 15 | { 16 | /** 17 | * @param non-empty-string|null $primaryKey Value of the primaryKey field supplied during index creation. `null` if it was not specified. 18 | */ 19 | public function __construct( 20 | public readonly ?string $primaryKey, 21 | ) { 22 | } 23 | 24 | public static function fromArray(array $data): self 25 | { 26 | return new self( 27 | $data['primaryKey'], 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Contracts/TaskDetails/IndexCreationDetails.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class IndexCreationDetails implements TaskDetails 15 | { 16 | /** 17 | * @param non-empty-string|null $primaryKey Value of the primaryKey field supplied during index creation. `null` if it was not specified. 18 | */ 19 | public function __construct( 20 | public readonly ?string $primaryKey, 21 | ) { 22 | } 23 | 24 | public static function fromArray(array $data): self 25 | { 26 | return new self( 27 | $data['primaryKey'], 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Contracts/Index/Settings.php: -------------------------------------------------------------------------------- 1 | getIterator()->getArrayCopy(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Contracts/TaskDetails/IndexDeletionDetails.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | final class IndexDeletionDetails implements TaskDetails 15 | { 16 | /** 17 | * @param non-negative-int|null $deletedDocuments Number of deleted documents. This should equal the total number of documents in the deleted index. `null` while the task status is enqueued or processing. 18 | */ 19 | public function __construct( 20 | public readonly ?int $deletedDocuments, 21 | ) { 22 | } 23 | 24 | public static function fromArray(array $data): self 25 | { 26 | return new self( 27 | $data['deletedDocuments'], 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /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'] = (object) $federation->toArray(); 29 | } 30 | 31 | return $this->http->post('/multi-search', $payload); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Endpoints/Delegates/HandlesNetwork.php: -------------------------------------------------------------------------------- 1 | network->get(); 20 | 21 | return new NetworkResults($response); 22 | } 23 | 24 | /** 25 | * @param array{ 26 | * self?: non-empty-string, 27 | * remotes?: array 28 | * } $network 29 | */ 30 | public function updateNetwork(array $network): NetworkResults 31 | { 32 | $response = $this->network->update($network); 33 | 34 | return new NetworkResults($response); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /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/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/TaskError.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class DocumentAdditionOrUpdateDetails implements TaskDetails 16 | { 17 | /** 18 | * @param non-negative-int $receivedDocuments number of documents received 19 | * @param non-negative-int|null $indexedDocuments Number of documents indexed. `null` while the task status is enqueued or processing. 20 | */ 21 | public function __construct( 22 | public readonly int $receivedDocuments, 23 | public readonly ?int $indexedDocuments, 24 | ) { 25 | } 26 | 27 | public static function fromArray(array $data): self 28 | { 29 | return new self( 30 | $data['receivedDocuments'], 31 | $data['indexedDocuments'] ?? null, 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Endpoints/Network.php: -------------------------------------------------------------------------------- 1 | 21 | * } 22 | */ 23 | public function get(): array 24 | { 25 | return $this->http->get(self::PATH); 26 | } 27 | 28 | /** 29 | * @param array{ 30 | * self?: non-empty-string, 31 | * remotes?: array 32 | * } $body 33 | * 34 | * @return array{ 35 | * self: non-empty-string, 36 | * remotes: array 37 | * } 38 | */ 39 | public function update(array $body): array 40 | { 41 | return $this->http->patch(self::PATH, $body); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Contracts/Data.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 | public function offsetGet($offset): mixed 32 | { 33 | return $this->data[$offset] ?? null; 34 | } 35 | 36 | public function count(): int 37 | { 38 | return \count($this->data); 39 | } 40 | 41 | public function getIterator(): \ArrayIterator 42 | { 43 | return new \ArrayIterator($this->data); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Endpoints/ChatWorkspaces.php: -------------------------------------------------------------------------------- 1 | workspaceName = $workspaceName; 26 | parent::__construct($http); 27 | } 28 | 29 | public function listWorkspaces(): ChatWorkspacesResults 30 | { 31 | $response = $this->http->get(self::PATH); 32 | 33 | return new ChatWorkspacesResults($response); 34 | } 35 | 36 | public function workspace(string $workspaceName): self 37 | { 38 | return new self($this->http, $workspaceName); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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): Task 33 | { 34 | return $this->tasks->deleteTasks($options); 35 | } 36 | 37 | public function cancelTasks(?CancelTasksQuery $options = null): Task 38 | { 39 | return $this->tasks->cancelTasks($options); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Contracts/HybridSearchOptions.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/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/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/TaskDetails/DocumentEditionDetails.php: -------------------------------------------------------------------------------- 1 | , 12 | * deletedDocuments: non-negative-int|null, 13 | * editedDocuments: non-negative-int|null, 14 | * function: string|null, 15 | * originalFilter: string|null 16 | * }> 17 | */ 18 | final class DocumentEditionDetails implements TaskDetails 19 | { 20 | /** 21 | * @param array $context 22 | */ 23 | public function __construct( 24 | public readonly array $context, 25 | public readonly ?int $deletedDocuments, 26 | public readonly ?int $editedDocuments, 27 | public readonly ?string $function, 28 | public readonly ?string $originalFilter, 29 | ) { 30 | } 31 | 32 | public static function fromArray(array $data): self 33 | { 34 | return new self( 35 | $data['context'], 36 | $data['deletedDocuments'], 37 | $data['editedDocuments'], 38 | $data['function'], 39 | $data['originalFilter'], 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Contracts/TaskDetails/DocumentDeletionDetails.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final class DocumentDeletionDetails implements TaskDetails 17 | { 18 | /** 19 | * @param non-negative-int|null $providedIds number of documents queued for deletion 20 | * @param string|null $originalFilter The filter used to delete documents. Null if it was not specified. 21 | * @param int|null $deletedDocuments Number of documents deleted. `null` while the task status is enqueued or processing. 22 | */ 23 | public function __construct( 24 | public readonly ?int $providedIds, 25 | public readonly ?string $originalFilter, 26 | public readonly ?int $deletedDocuments, 27 | ) { 28 | } 29 | 30 | public static function fromArray(array $data): self 31 | { 32 | return new self( 33 | $data['providedIds'] ?? null, 34 | $data['originalFilter'] ?? null, 35 | $data['deletedDocuments'] ?? null, 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /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/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/NetworkResults.php: -------------------------------------------------------------------------------- 1 | a mapping of remote node IDs to their connection details 19 | */ 20 | private array $remotes; 21 | 22 | /** 23 | * @param array{ 24 | * self?: non-empty-string, 25 | * remotes?: array 26 | * } $params 27 | */ 28 | public function __construct(array $params) 29 | { 30 | parent::__construct($params); 31 | 32 | $this->self = $params['self'] ?? ''; 33 | $this->remotes = $params['remotes'] ?? []; 34 | } 35 | 36 | /** 37 | * @return non-empty-string the identifier for the local node 38 | */ 39 | public function getSelf(): string 40 | { 41 | return $this->self; 42 | } 43 | 44 | /** 45 | * @return array 46 | */ 47 | public function getRemotes(): array 48 | { 49 | return $this->remotes; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Exceptions/InvalidResponseBodyException.php: -------------------------------------------------------------------------------- 1 | httpStatus = $response->getStatusCode(); 19 | 20 | parent::__construct($this->getMessageFromHttpBody() ?? $response->getReasonPhrase(), $this->httpStatus, $previous); 21 | } 22 | 23 | public function __toString(): string 24 | { 25 | $base = 'Meilisearch InvalidResponseBodyException: Http Status: '.$this->httpStatus; 26 | 27 | if ('' !== $this->message) { 28 | $base .= ' - Message: '.$this->message; 29 | } 30 | 31 | return $base; 32 | } 33 | 34 | public function getMessageFromHttpBody(): ?string 35 | { 36 | if (null !== $this->httpBody) { 37 | $rawText = strip_tags($this->httpBody); 38 | 39 | if (!ctype_space($rawText)) { 40 | return substr(trim($rawText), 0, 100); 41 | } 42 | } 43 | 44 | return null; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /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/TaskDetails/TaskDeletionDetails.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final class TaskDeletionDetails implements TaskDetails 17 | { 18 | /** 19 | * @param non-negative-int|null $matchedTasks The number of matched tasks. If the API key used for the request doesn’t have access to an index, tasks relating to that index will not be included in matchedTasks. 20 | * @param non-negative-int|null $deletedTasks The number of tasks successfully deleted. If the task deletion fails, this will be 0. null when the task status is enqueued or processing. 21 | * @param string|null $originalFilter the filter used in the delete task request 22 | */ 23 | public function __construct( 24 | public readonly ?int $matchedTasks, 25 | public readonly ?int $deletedTasks, 26 | public readonly ?string $originalFilter, 27 | ) { 28 | } 29 | 30 | public static function fromArray(array $data): self 31 | { 32 | return new self( 33 | $data['matchedTasks'], 34 | $data['deletedTasks'], 35 | $data['originalFilter'], 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Contracts/TaskDetails/TaskCancelationDetails.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final class TaskCancelationDetails implements TaskDetails 17 | { 18 | /** 19 | * @param non-negative-int|null $matchedTasks The number of matched tasks. If the API key used for the request doesn’t have access to an index, tasks relating to that index will not be included in matchedTasks. 20 | * @param non-negative-int|null $canceledTasks The number of tasks successfully canceled. If the task cancellation fails, this will be 0. null when the task status is enqueued or processing. 21 | * @param string|null $originalFilter the filter used in the cancel task request 22 | */ 23 | public function __construct( 24 | public readonly ?int $matchedTasks, 25 | public readonly ?int $canceledTasks, 26 | public readonly ?string $originalFilter, 27 | ) { 28 | } 29 | 30 | public static function fromArray(array $data): self 31 | { 32 | return new self( 33 | $data['matchedTasks'], 34 | $data['canceledTasks'], 35 | $data['originalFilter'], 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Contracts/Http.php: -------------------------------------------------------------------------------- 1 | commitSha; 26 | } 27 | 28 | public function getCommitDate(): ?\DateTimeImmutable 29 | { 30 | return $this->commitDate; 31 | } 32 | 33 | public function getPkgVersion(): string 34 | { 35 | return $this->pkgVersion; 36 | } 37 | 38 | /** 39 | * @param array{ 40 | * commitSha: non-empty-string, 41 | * commitDate: non-empty-string, 42 | * pkgVersion: non-empty-string 43 | * } $data 44 | */ 45 | public static function fromArray(array $data): Version 46 | { 47 | $commitDate = null; 48 | if ('unknown' !== $data['commitDate']) { 49 | $commitDate = new \DateTimeImmutable($data['commitDate']); 50 | } 51 | 52 | return new self( 53 | $data['commitSha'], 54 | $commitDate, 55 | $data['pkgVersion'], 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /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/Search/FacetSearchResult.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 | JSON_THROW_ON_ERROR); 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/Endpoints/Delegates/HandlesIndex.php: -------------------------------------------------------------------------------- 1 | index->all($options ?? null); 19 | } 20 | 21 | /** 22 | * @param non-empty-string $uid 23 | */ 24 | public function getRawIndex(string $uid): array 25 | { 26 | return $this->index($uid)->fetchRawInfo(); 27 | } 28 | 29 | /** 30 | * @param non-empty-string $uid 31 | */ 32 | public function index(string $uid): Indexes 33 | { 34 | return new Indexes($this->http, $uid); 35 | } 36 | 37 | /** 38 | * @param non-empty-string $uid 39 | */ 40 | public function getIndex(string $uid): Indexes 41 | { 42 | return $this->index($uid)->fetchInfo(); 43 | } 44 | 45 | /** 46 | * @param non-empty-string $uid 47 | */ 48 | public function deleteIndex(string $uid): Task 49 | { 50 | return $this->index($uid)->delete(); 51 | } 52 | 53 | /** 54 | * @param non-empty-string $uid 55 | */ 56 | public function createIndex(string $uid, array $options = []): Task 57 | { 58 | return $this->index->create($uid, $options); 59 | } 60 | 61 | /** 62 | * @param non-empty-string $uid 63 | */ 64 | public function updateIndex(string $uid, array $options = []): Task 65 | { 66 | return $this->index($uid)->update($options); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Contracts/Stats.php: -------------------------------------------------------------------------------- 1 | $indexes 13 | */ 14 | public function __construct( 15 | private readonly int $databaseSize, 16 | private readonly int $usedDatabaseSize, 17 | private readonly ?\DateTimeImmutable $lastUpdate, 18 | private readonly array $indexes, 19 | ) { 20 | } 21 | 22 | /** 23 | * @return non-negative-int 24 | */ 25 | public function getDatabaseSize(): int 26 | { 27 | return $this->databaseSize; 28 | } 29 | 30 | /** 31 | * @return non-negative-int 32 | */ 33 | public function getUsedDatabaseSize(): int 34 | { 35 | return $this->usedDatabaseSize; 36 | } 37 | 38 | public function getLastUpdate(): ?\DateTimeImmutable 39 | { 40 | return $this->lastUpdate; 41 | } 42 | 43 | /** 44 | * @return array 45 | */ 46 | public function getIndexes(): array 47 | { 48 | return $this->indexes; 49 | } 50 | 51 | /** 52 | * @param array{ 53 | * databaseSize: non-negative-int, 54 | * usedDatabaseSize: non-negative-int, 55 | * lastUpdate: non-empty-string|null, 56 | * indexes: array 57 | * } $data 58 | */ 59 | public static function fromArray(array $data): self 60 | { 61 | return new self( 62 | $data['databaseSize'], 63 | $data['usedDatabaseSize'], 64 | null !== $data['lastUpdate'] ? new \DateTimeImmutable($data['lastUpdate']) : null, 65 | array_map(static fn (array $v) => IndexStats::fromArray($v), $data['indexes']), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Contracts/ChatWorkspacesResults.php: -------------------------------------------------------------------------------- 1 | offset = $params['offset']; 29 | $this->limit = $params['limit']; 30 | $this->total = $params['total']; 31 | } 32 | 33 | /** 34 | * @return array 35 | */ 36 | public function getResults(): array 37 | { 38 | return $this->data; 39 | } 40 | 41 | /** 42 | * @return non-negative-int 43 | */ 44 | public function getOffset(): int 45 | { 46 | return $this->offset; 47 | } 48 | 49 | /** 50 | * @return non-negative-int 51 | */ 52 | public function getLimit(): int 53 | { 54 | return $this->limit; 55 | } 56 | 57 | /** 58 | * @return non-negative-int 59 | */ 60 | public function getTotal(): int 61 | { 62 | return $this->total; 63 | } 64 | 65 | /** 66 | * @return array{ 67 | * results: array, 68 | * offset: non-negative-int, 69 | * limit: non-negative-int, 70 | * total: non-negative-int 71 | * } 72 | */ 73 | public function toArray(): array 74 | { 75 | return [ 76 | 'results' => $this->data, 77 | 'offset' => $this->offset, 78 | 'limit' => $this->limit, 79 | 'total' => $this->total, 80 | ]; 81 | } 82 | 83 | /** 84 | * @return non-negative-int 85 | */ 86 | public function count(): int 87 | { 88 | return \count($this->data); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /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/Endpoints/Delegates/HandlesSystem.php: -------------------------------------------------------------------------------- 1 | health->show(); 26 | } 27 | 28 | public function isHealthy(): bool 29 | { 30 | try { 31 | $this->health->show(); 32 | } catch (\Exception $e) { 33 | return false; 34 | } 35 | 36 | return true; 37 | } 38 | 39 | public function version(): VersionContract 40 | { 41 | $version = $this->version->show(); 42 | 43 | if (!\is_array($version)) { 44 | throw new LogicException('Version did not respond with valid data.'); 45 | } 46 | 47 | return VersionContract::fromArray($version); 48 | } 49 | 50 | public function stats(): StatsContract 51 | { 52 | $stats = $this->stats->show(); 53 | 54 | if (!\is_array($stats)) { 55 | throw new LogicException('Stats did not respond with valid data.'); 56 | } 57 | 58 | return StatsContract::fromArray($stats); 59 | } 60 | 61 | public function generateTenantToken(string $apiKeyUid, $searchRules, array $options = []): string 62 | { 63 | return $this->tenantToken->generateTenantToken($apiKeyUid, $searchRules, $options); 64 | } 65 | 66 | public function swapIndexes(array $indexes): Task 67 | { 68 | $options = array_map(static fn ($data) => ['indexes' => $data], $indexes); 69 | 70 | return $this->index->swapIndexes($options); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Endpoints/Tasks.php: -------------------------------------------------------------------------------- 1 | http->get(self::PATH.'/'.$taskUid), partial(self::waitTask(...), $this->http)); 23 | } 24 | 25 | // @todo: must return array 26 | public function all(array $query = []): array 27 | { 28 | return $this->http->get(self::PATH.'/', $query); 29 | } 30 | 31 | public function cancelTasks(?CancelTasksQuery $options): Task 32 | { 33 | $options = $options ?? new CancelTasksQuery(); 34 | 35 | return Task::fromArray($this->http->post('/tasks/cancel', null, $options->toArray()), partial(self::waitTask(...), $this->http)); 36 | } 37 | 38 | public function deleteTasks(?DeleteTasksQuery $options): Task 39 | { 40 | $options = $options ?? new DeleteTasksQuery(); 41 | 42 | return Task::fromArray($this->http->delete(self::PATH, $options->toArray()), partial(self::waitTask(...), $this->http)); 43 | } 44 | 45 | /** 46 | * @internal 47 | * 48 | * @throws TimeOutException 49 | */ 50 | public static function waitTask(Http $http, int $taskUid, int $timeoutInMs, int $intervalInMs): Task 51 | { 52 | $timeoutTemp = 0; 53 | 54 | while ($timeoutInMs > $timeoutTemp) { 55 | $task = Task::fromArray($http->get(self::PATH.'/'.$taskUid), partial(self::waitTask(...), $http)); 56 | 57 | if ($task->isFinished()) { 58 | return $task; 59 | } 60 | 61 | $timeoutTemp += $intervalInMs; 62 | usleep(1000 * $intervalInMs); 63 | } 64 | 65 | throw new TimeOutException(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Contracts/ChatWorkspacePromptsSettings.php: -------------------------------------------------------------------------------- 1 | system = $params['system']; 36 | $this->searchDescription = $params['searchDescription']; 37 | $this->searchQParam = $params['searchQParam']; 38 | $this->searchIndexUidParam = $params['searchIndexUidParam']; 39 | } 40 | 41 | public function getSystem(): string 42 | { 43 | return $this->system; 44 | } 45 | 46 | public function getSearchDescription(): string 47 | { 48 | return $this->searchDescription; 49 | } 50 | 51 | /** 52 | * @return non-empty-string 53 | */ 54 | public function getSearchQParam(): string 55 | { 56 | return $this->searchQParam; 57 | } 58 | 59 | /** 60 | * @return non-empty-string 61 | */ 62 | public function getSearchIndexUidParam(): string 63 | { 64 | return $this->searchIndexUidParam; 65 | } 66 | 67 | /** 68 | * @return ChatWorkspacePromptsArray 69 | */ 70 | public function toArray(): array 71 | { 72 | return [ 73 | 'system' => $this->system, 74 | 'searchDescription' => $this->searchDescription, 75 | 'searchQParam' => $this->searchQParam, 76 | 'searchIndexUidParam' => $this->searchIndexUidParam, 77 | ]; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Contracts/TasksResults.php: -------------------------------------------------------------------------------- 1 | Task::fromArray($data), $params['results']) : []); 32 | 33 | $this->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/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/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 | -------------------------------------------------------------------------------- /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": "Clémentine Urquizar", 17 | "email": "clementine@meilisearch.com" 18 | }, 19 | { 20 | "name": "Bruno Casali", 21 | "email": "bruno@meilisearch.com" 22 | }, 23 | { 24 | "name": "Laurent Cazanove", 25 | "email": "lau.cazanove@gmail.com" 26 | }, 27 | { 28 | "name": "Tomas Norkūnas", 29 | "email": "norkunas.tom@gmail.com" 30 | } 31 | ], 32 | "minimum-stability": "stable", 33 | "require": { 34 | "php": "^8.1", 35 | "ext-json": "*", 36 | "php-http/discovery": "^1.19", 37 | "psr/http-client": "^1.0", 38 | "symfony/polyfill-php81": "^1.33" 39 | }, 40 | "autoload": { 41 | "psr-4": { 42 | "Meilisearch\\": "src/" 43 | }, 44 | "files": [ 45 | "src/functions.php" 46 | ] 47 | }, 48 | "autoload-dev": { 49 | "psr-4": { 50 | "Tests\\": "tests/" 51 | } 52 | }, 53 | "suggest": { 54 | "guzzlehttp/guzzle": "Use Guzzle ^7 as HTTP client", 55 | "http-interop/http-factory-guzzle": "Factory for guzzlehttp/guzzle", 56 | "symfony/http-client": "Use Symfony Http client" 57 | }, 58 | "require-dev": { 59 | "phpunit/phpunit": "^10.5", 60 | "php-cs-fixer/shim": "^3.59.3", 61 | "http-interop/http-factory-guzzle": "^1.2.0", 62 | "phpstan/phpstan": "^2.0", 63 | "phpstan/phpstan-phpunit": "^2.0", 64 | "phpstan/phpstan-deprecation-rules": "^2.0", 65 | "phpstan/phpstan-strict-rules": "^2.0", 66 | "symfony/http-client": "^5.4|^6.0|^7.0|^8.0" 67 | }, 68 | "scripts": { 69 | "lint": [ 70 | "./vendor/bin/php-cs-fixer fix --verbose --config=.php-cs-fixer.dist.php --using-cache=no --dry-run --diff" 71 | ], 72 | "lint:fix": [ 73 | "./vendor/bin/php-cs-fixer fix --verbose --config=.php-cs-fixer.dist.php --using-cache=no --diff" 74 | ], 75 | "phpstan": "./vendor/bin/phpstan", 76 | "test": [ 77 | "sh scripts/tests.sh" 78 | ] 79 | }, 80 | "config": { 81 | "allow-plugins": { 82 | "php-http/discovery": true 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | $clientAgents 49 | */ 50 | public function __construct( 51 | string $url, 52 | ?string $apiKey = null, 53 | ?ClientInterface $httpClient = null, 54 | ?RequestFactoryInterface $requestFactory = null, 55 | array $clientAgents = [], 56 | ?StreamFactoryInterface $streamFactory = null, 57 | ) { 58 | $this->http = new MeilisearchClientAdapter($url, $apiKey, $httpClient, $requestFactory, $clientAgents, $streamFactory); 59 | $this->chats = new ChatWorkspaces($this->http); 60 | $this->index = new Indexes($this->http); 61 | $this->health = new Health($this->http); 62 | $this->version = new Version($this->http); 63 | $this->stats = new Stats($this->http); 64 | $this->tasks = new Tasks($this->http); 65 | $this->batches = new Batches($this->http); 66 | $this->keys = new Keys($this->http); 67 | $this->dumps = new Dumps($this->http); 68 | $this->snapshots = new Snapshots($this->http); 69 | $this->tenantToken = new TenantToken($this->http, $apiKey); 70 | $this->network = new Network($this->http); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Endpoints/Delegates/HandlesChatWorkspaceSettings.php: -------------------------------------------------------------------------------- 1 | workspaceName) { 18 | throw new \InvalidArgumentException('Workspace name is required to get settings'); 19 | } 20 | 21 | $response = $this->http->get('/chats/'.$this->workspaceName.'/settings'); 22 | 23 | return new ChatWorkspaceSettings($response); 24 | } 25 | 26 | /** 27 | * Update the settings for this chat workspace. 28 | * 29 | * @param array{ 30 | * source?: 'openAi'|'azureOpenAi'|'mistral'|'gemini'|'vLlm', 31 | * orgId?: string, 32 | * projectId?: string, 33 | * apiVersion?: string, 34 | * deploymentId?: string, 35 | * baseUrl?: string, 36 | * apiKey?: string, 37 | * prompts?: array 38 | * } $settings 39 | */ 40 | public function updateSettings(array $settings): ChatWorkspaceSettings 41 | { 42 | if (null === $this->workspaceName) { 43 | throw new \InvalidArgumentException('Workspace name is required to update settings'); 44 | } 45 | 46 | $response = $this->http->patch('/chats/'.$this->workspaceName.'/settings', $settings); 47 | 48 | return new ChatWorkspaceSettings($response); 49 | } 50 | 51 | /** 52 | * Reset the settings for this chat workspace to default values. 53 | */ 54 | public function resetSettings(): ChatWorkspaceSettings 55 | { 56 | if (null === $this->workspaceName) { 57 | throw new \InvalidArgumentException('Workspace name is required to reset settings'); 58 | } 59 | 60 | $response = $this->http->delete('/chats/'.$this->workspaceName.'/settings'); 61 | 62 | return new ChatWorkspaceSettings($response); 63 | } 64 | 65 | /** 66 | * Create a streaming chat completion using OpenAI-compatible API. 67 | * 68 | * @param array{ 69 | * model: string, 70 | * messages: array, 71 | * stream: bool 72 | * } $options The request body for the chat completion 73 | */ 74 | public function streamCompletion(array $options): StreamInterface 75 | { 76 | if (null === $this->workspaceName) { 77 | throw new \InvalidArgumentException('Workspace name is required for chat completion'); 78 | } 79 | 80 | return $this->http->postStream('/chats/'.$this->workspaceName.'/chat/completions', $options); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Exceptions/ApiException.php: -------------------------------------------------------------------------------- 1 | httpStatus = $response->getStatusCode(); 24 | $this->errorCode = $this->getErrorCodeFromHttpBody(); 25 | $this->errorLink = $this->getErrorLinkFromHttpBody(); 26 | $this->errorType = $this->getErrorTypeFromHttpBody(); 27 | 28 | parent::__construct($this->getMessageFromHttpBody() ?? $response->getReasonPhrase(), $this->httpStatus, $previous); 29 | } 30 | 31 | public function __toString(): string 32 | { 33 | $base = 'Meilisearch ApiException: Http Status: '.$this->httpStatus; 34 | 35 | if ('' !== $this->message) { 36 | $base .= ' - Message: '.$this->message; 37 | } 38 | 39 | if (!\is_null($this->errorCode)) { 40 | $base .= ' - Code: '.$this->errorCode; 41 | } 42 | 43 | if (!\is_null($this->errorType)) { 44 | $base .= ' - Type: '.$this->errorType; 45 | } 46 | 47 | if (!\is_null($this->errorLink)) { 48 | $base .= ' - Link: '.$this->errorLink; 49 | } 50 | 51 | return $base; 52 | } 53 | 54 | private function getMessageFromHttpBody(): ?string 55 | { 56 | if (\is_array($this->httpBody) && \array_key_exists('message', $this->httpBody)) { 57 | return $this->httpBody['message']; 58 | } 59 | 60 | return null; 61 | } 62 | 63 | private function getErrorCodeFromHttpBody(): ?string 64 | { 65 | if (\is_array($this->httpBody) && \array_key_exists('code', $this->httpBody)) { 66 | return $this->httpBody['code']; 67 | } 68 | 69 | return null; 70 | } 71 | 72 | private function getErrorTypeFromHttpBody(): ?string 73 | { 74 | if (\is_array($this->httpBody) && \array_key_exists('type', $this->httpBody)) { 75 | return $this->httpBody['type']; 76 | } 77 | 78 | return null; 79 | } 80 | 81 | private function getErrorLinkFromHttpBody(): ?string 82 | { 83 | if (\is_array($this->httpBody) && \array_key_exists('link', $this->httpBody)) { 84 | return $this->httpBody['link']; 85 | } 86 | 87 | return null; 88 | } 89 | 90 | public static function rethrowWithHint(\Throwable $e, string $methodName): \Exception 91 | { 92 | return new \RuntimeException(\sprintf(self::HINT_MESSAGE, $methodName), 0, $e); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/Contracts/IndexStats.php: -------------------------------------------------------------------------------- 1 | $fieldDistribution 16 | */ 17 | public function __construct( 18 | private readonly int $numberOfDocuments, 19 | private readonly int $rawDocumentDbSize, 20 | private readonly int $avgDocumentSize, 21 | private readonly bool $isIndexing, 22 | private readonly int $numberOfEmbeddings, 23 | private readonly int $numberOfEmbeddedDocuments, 24 | private readonly array $fieldDistribution, 25 | ) { 26 | } 27 | 28 | /** 29 | * @return non-negative-int 30 | */ 31 | public function getNumberOfDocuments(): int 32 | { 33 | return $this->numberOfDocuments; 34 | } 35 | 36 | /** 37 | * @return non-negative-int 38 | */ 39 | public function getRawDocumentDbSize(): int 40 | { 41 | return $this->rawDocumentDbSize; 42 | } 43 | 44 | /** 45 | * @return non-negative-int 46 | */ 47 | public function getAvgDocumentSize(): int 48 | { 49 | return $this->avgDocumentSize; 50 | } 51 | 52 | public function isIndexing(): bool 53 | { 54 | return $this->isIndexing; 55 | } 56 | 57 | /** 58 | * @return non-negative-int 59 | */ 60 | public function getNumberOfEmbeddings(): int 61 | { 62 | return $this->numberOfEmbeddings; 63 | } 64 | 65 | /** 66 | * @return non-negative-int 67 | */ 68 | public function getNumberOfEmbeddedDocuments(): int 69 | { 70 | return $this->numberOfEmbeddedDocuments; 71 | } 72 | 73 | /** 74 | * @return array 75 | */ 76 | public function getFieldDistribution(): array 77 | { 78 | return $this->fieldDistribution; 79 | } 80 | 81 | /** 82 | * @param array{ 83 | * numberOfDocuments: non-negative-int, 84 | * rawDocumentDbSize: non-negative-int, 85 | * avgDocumentSize: non-negative-int, 86 | * isIndexing: bool, 87 | * numberOfEmbeddings: non-negative-int, 88 | * numberOfEmbeddedDocuments: non-negative-int, 89 | * fieldDistribution: array 90 | * } $data 91 | */ 92 | public static function fromArray(array $data): self 93 | { 94 | return new self( 95 | $data['numberOfDocuments'], 96 | $data['rawDocumentDbSize'], 97 | $data['avgDocumentSize'], 98 | $data['isIndexing'], 99 | $data['numberOfEmbeddings'], 100 | $data['numberOfEmbeddedDocuments'], 101 | $data['fieldDistribution'], 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /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, array|object $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/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/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 | * @var list|null 38 | */ 39 | private ?array $sort = null; 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 non-negative-int $limit 55 | * 56 | * @return $this 57 | */ 58 | public function setLimit(int $limit): self 59 | { 60 | $this->limit = $limit; 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * @param non-empty-list $fields 67 | * 68 | * @return $this 69 | */ 70 | public function setFields(array $fields): self 71 | { 72 | $this->fields = $fields; 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * Sets the filter for the DocumentsQuery. 79 | * 80 | * @param list> $filter a filter expression written as an array of strings 81 | * 82 | * @return $this 83 | */ 84 | public function setFilter(array $filter): self 85 | { 86 | $this->filter = $filter; 87 | 88 | return $this; 89 | } 90 | 91 | /** 92 | * @param bool|null $retrieveVectors boolean value to show _vector details 93 | * 94 | * @return $this 95 | */ 96 | public function setRetrieveVectors(?bool $retrieveVectors): self 97 | { 98 | $this->retrieveVectors = $retrieveVectors; 99 | 100 | return $this; 101 | } 102 | 103 | /** 104 | * @param list $ids Array of document IDs 105 | * 106 | * @return $this 107 | */ 108 | public function setIds(array $ids): self 109 | { 110 | $this->ids = $ids; 111 | 112 | return $this; 113 | } 114 | 115 | /** 116 | * Checks if the $filter attribute has been set. 117 | * 118 | * @return bool true when filter contains at least a non-empty array 119 | */ 120 | public function hasFilter(): bool 121 | { 122 | return null !== $this->filter; 123 | } 124 | 125 | /** 126 | * @param list $sort 127 | */ 128 | public function setSort(array $sort): self 129 | { 130 | $this->sort = $sort; 131 | 132 | return $this; 133 | } 134 | 135 | /** 136 | * @return array{ 137 | * offset?: non-negative-int, 138 | * limit?: non-negative-int, 139 | * fields?: non-empty-list|non-empty-string, 140 | * filter?: list>, 141 | * retrieveVectors?: bool, 142 | * ids?: string, 143 | * sort?: non-empty-list, 144 | * } 145 | */ 146 | public function toArray(): array 147 | { 148 | return array_filter([ 149 | 'offset' => $this->offset, 150 | 'limit' => $this->limit, 151 | 'fields' => $this->fields, 152 | 'filter' => $this->filter, 153 | 'retrieveVectors' => $this->retrieveVectors, 154 | 'ids' => ($this->ids ?? []) !== [] ? implode(',', $this->ids) : null, 155 | 'sort' => $this->sort, 156 | ], static function ($item) { return null !== $item; }); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /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 $date?->format(\DateTimeInterface::RFC3339); 152 | } 153 | 154 | private function formatArray(?array $array): ?string 155 | { 156 | return null !== $array ? implode(',', $array) : null; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/Contracts/ChatWorkspaceSettings.php: -------------------------------------------------------------------------------- 1 | source = $params['source']; 61 | $this->orgId = $params['orgId'] ?? null; 62 | $this->projectId = $params['projectId'] ?? null; 63 | $this->apiVersion = $params['apiVersion'] ?? null; 64 | $this->deploymentId = $params['deploymentId'] ?? null; 65 | $this->baseUrl = $params['baseUrl'] ?? null; 66 | $this->apiKey = $params['apiKey'] ?? null; 67 | $this->prompts = new ChatWorkspacePromptsSettings($params['prompts']); 68 | } 69 | 70 | /** 71 | * @return ChatWorkspaceSource 72 | */ 73 | public function getSource(): string 74 | { 75 | return $this->source; 76 | } 77 | 78 | /** 79 | * @return non-empty-string|null 80 | */ 81 | public function getOrgId(): ?string 82 | { 83 | return $this->orgId; 84 | } 85 | 86 | /** 87 | * @return non-empty-string|null 88 | */ 89 | public function getProjectId(): ?string 90 | { 91 | return $this->projectId; 92 | } 93 | 94 | /** 95 | * @return non-empty-string|null 96 | */ 97 | public function getApiVersion(): ?string 98 | { 99 | return $this->apiVersion; 100 | } 101 | 102 | /** 103 | * @return non-empty-string|null 104 | */ 105 | public function getDeploymentId(): ?string 106 | { 107 | return $this->deploymentId; 108 | } 109 | 110 | /** 111 | * @return non-empty-string|null 112 | */ 113 | public function getBaseUrl(): ?string 114 | { 115 | return $this->baseUrl; 116 | } 117 | 118 | /** 119 | * @return non-empty-string|null 120 | */ 121 | public function getApiKey(): ?string 122 | { 123 | return $this->apiKey; 124 | } 125 | 126 | public function getPrompts(): ChatWorkspacePromptsSettings 127 | { 128 | return $this->prompts; 129 | } 130 | 131 | /** 132 | * @return array{ 133 | * source: ChatWorkspaceSource, 134 | * orgId?: non-empty-string, 135 | * projectId?: non-empty-string, 136 | * apiVersion?: non-empty-string, 137 | * deploymentId?: non-empty-string, 138 | * baseUrl?: non-empty-string, 139 | * apiKey?: string, 140 | * prompts: array{ 141 | * system: string, 142 | * searchDescription: string, 143 | * searchQParam: non-empty-string, 144 | * searchIndexUidParam: non-empty-string 145 | * } 146 | * } 147 | */ 148 | public function toArray(): array 149 | { 150 | return [ 151 | 'source' => $this->source, 152 | 'orgId' => $this->orgId, 153 | 'projectId' => $this->projectId, 154 | 'apiVersion' => $this->apiVersion, 155 | 'deploymentId' => $this->deploymentId, 156 | 'baseUrl' => $this->baseUrl, 157 | 'apiKey' => $this->apiKey, 158 | 'prompts' => $this->prompts->toArray(), 159 | ]; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /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 | private int|float|null $rankingScoreThreshold = null; 46 | 47 | /** 48 | * @param int|non-empty-string $id 49 | * @param non-empty-string $embedder 50 | */ 51 | public function __construct(int|string $id, string $embedder) 52 | { 53 | $this->id = $id; 54 | $this->embedder = $embedder; 55 | } 56 | 57 | /** 58 | * @param non-negative-int|null $offset 59 | * 60 | * @return $this 61 | */ 62 | public function setOffset(?int $offset): self 63 | { 64 | $this->offset = $offset; 65 | 66 | return $this; 67 | } 68 | 69 | /** 70 | * @param positive-int|null $limit 71 | * 72 | * @return $this 73 | */ 74 | public function setLimit(?int $limit): self 75 | { 76 | $this->limit = $limit; 77 | 78 | return $this; 79 | } 80 | 81 | /** 82 | * @param array|string> $filter an array of arrays representing filter conditions 83 | * 84 | * @return $this 85 | */ 86 | public function setFilter(array $filter): self 87 | { 88 | $this->filter = $filter; 89 | 90 | return $this; 91 | } 92 | 93 | /** 94 | * @param list $attributesToRetrieve an array of attribute names to retrieve 95 | * 96 | * @return $this 97 | */ 98 | public function setAttributesToRetrieve(array $attributesToRetrieve): self 99 | { 100 | $this->attributesToRetrieve = $attributesToRetrieve; 101 | 102 | return $this; 103 | } 104 | 105 | /** 106 | * @param bool|null $showRankingScore boolean value to show ranking score 107 | * 108 | * @return $this 109 | */ 110 | public function setShowRankingScore(?bool $showRankingScore): self 111 | { 112 | $this->showRankingScore = $showRankingScore; 113 | 114 | return $this; 115 | } 116 | 117 | /** 118 | * @param bool|null $showRankingScoreDetails boolean value to show ranking score details 119 | * 120 | * @return $this 121 | */ 122 | public function setShowRankingScoreDetails(?bool $showRankingScoreDetails): self 123 | { 124 | $this->showRankingScoreDetails = $showRankingScoreDetails; 125 | 126 | return $this; 127 | } 128 | 129 | /** 130 | * @param bool|null $retrieveVectors boolean value to show _vector details 131 | * 132 | * @return $this 133 | */ 134 | public function setRetrieveVectors(?bool $retrieveVectors): self 135 | { 136 | $this->retrieveVectors = $retrieveVectors; 137 | 138 | return $this; 139 | } 140 | 141 | /** 142 | * @return $this 143 | */ 144 | public function setRankingScoreThreshold(int|float|null $rankingScoreThreshold): self 145 | { 146 | $this->rankingScoreThreshold = $rankingScoreThreshold; 147 | 148 | return $this; 149 | } 150 | 151 | /** 152 | * @return array{ 153 | * id: int|non-empty-string, 154 | * embedder: non-empty-string, 155 | * offset?: non-negative-int, 156 | * limit?: positive-int, 157 | * filter?: array|string>, 158 | * attributesToRetrieve?: list, 159 | * showRankingScore?: bool, 160 | * showRankingScoreDetails?: bool, 161 | * retrieveVectors?: bool, 162 | * rankingScoreThreshold?: int|float 163 | * } 164 | */ 165 | public function toArray(): array 166 | { 167 | return array_filter([ 168 | 'id' => $this->id, 169 | 'embedder' => $this->embedder, 170 | 'offset' => $this->offset, 171 | 'limit' => $this->limit, 172 | 'filter' => $this->filter, 173 | 'attributesToRetrieve' => $this->attributesToRetrieve, 174 | 'showRankingScore' => $this->showRankingScore, 175 | 'showRankingScoreDetails' => $this->showRankingScoreDetails, 176 | 'retrieveVectors' => $this->retrieveVectors, 177 | 'rankingScoreThreshold' => $this->rankingScoreThreshold, 178 | ], static function ($item) {return null !== $item; }); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /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 = new \DateTimeImmutable($attributes['expiresAt']); 57 | } 58 | if ($attributes['createdAt']) { 59 | $key->createdAt = new \DateTimeImmutable($attributes['createdAt']); 60 | } 61 | if ($attributes['updatedAt']) { 62 | $key->updatedAt = new \DateTimeImmutable($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 = new \DateTimeImmutable($attributes['expiresAt']); 81 | } 82 | if ($attributes['createdAt']) { 83 | $this->createdAt = new \DateTimeImmutable($attributes['createdAt']); 84 | } 85 | if ($attributes['updatedAt']) { 86 | $this->updatedAt = new \DateTimeImmutable($attributes['updatedAt']); 87 | } 88 | 89 | return $this; 90 | } 91 | 92 | public function getUid(): ?string 93 | { 94 | return $this->uid; 95 | } 96 | 97 | public function getName(): ?string 98 | { 99 | return $this->name; 100 | } 101 | 102 | public function getKey(): ?string 103 | { 104 | return $this->key; 105 | } 106 | 107 | public function getDescription(): ?string 108 | { 109 | return $this->description; 110 | } 111 | 112 | public function getActions(): ?array 113 | { 114 | return $this->actions; 115 | } 116 | 117 | public function getIndexes(): ?array 118 | { 119 | return $this->indexes; 120 | } 121 | 122 | public function getExpiresAt(): ?\DateTimeInterface 123 | { 124 | return $this->expiresAt; 125 | } 126 | 127 | public function getCreatedAt(): ?\DateTimeInterface 128 | { 129 | return $this->createdAt; 130 | } 131 | 132 | public function getUpdatedAt(): ?\DateTimeInterface 133 | { 134 | return $this->updatedAt; 135 | } 136 | 137 | /** 138 | * @param non-empty-string $keyOrUid 139 | */ 140 | public function get(string $keyOrUid): self 141 | { 142 | $response = $this->http->get(self::PATH.'/'.$keyOrUid); 143 | 144 | return $this->fill($response); 145 | } 146 | 147 | public function all(?KeysQuery $options = null): KeysResults 148 | { 149 | $query = isset($options) ? $options->toArray() : []; 150 | 151 | $keys = []; 152 | $response = $this->allRaw($query); 153 | 154 | foreach ($response['results'] as $key) { 155 | $keys[] = $this->newInstance($key); 156 | } 157 | 158 | $response['results'] = $keys; 159 | 160 | return new KeysResults($response); 161 | } 162 | 163 | public function allRaw(array $options = []): array 164 | { 165 | return $this->http->get(self::PATH.'/', $options); 166 | } 167 | 168 | public function create(array $options = []): self 169 | { 170 | if (isset($options['expiresAt']) && $options['expiresAt'] instanceof \DateTimeInterface) { 171 | $options['expiresAt'] = $options['expiresAt']->format('Y-m-d\TH:i:s.vu\Z'); 172 | } 173 | $response = $this->http->post(self::PATH, $options); 174 | 175 | return $this->fill($response); 176 | } 177 | 178 | /** 179 | * @param non-empty-string $keyOrUid 180 | */ 181 | public function update(string $keyOrUid, array $options = []): self 182 | { 183 | $data = array_intersect_key($options, array_flip(['description', 'name'])); 184 | $response = $this->http->patch(self::PATH.'/'.$keyOrUid, $data); 185 | 186 | return $this->fill($response); 187 | } 188 | 189 | /** 190 | * @param non-empty-string $keyOrUid 191 | */ 192 | public function delete(string $keyOrUid): array 193 | { 194 | return $this->http->delete(self::PATH.'/'.$keyOrUid) ?? []; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /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 | 'facetStats' => $this->facetStats, 227 | ]; 228 | 229 | if (!$this->numberedPagination) { 230 | $arr = array_merge($arr, [ 231 | 'offset' => $this->offset, 232 | 'limit' => $this->limit, 233 | 'estimatedTotalHits' => $this->estimatedTotalHits, 234 | ]); 235 | } else { 236 | $arr = array_merge($arr, [ 237 | 'hitsPerPage' => $this->hitsPerPage, 238 | 'page' => $this->page, 239 | 'totalPages' => $this->totalPages, 240 | 'totalHits' => $this->totalHits, 241 | ]); 242 | } 243 | 244 | return $arr; 245 | } 246 | 247 | public function toJSON(): string 248 | { 249 | return json_encode($this->toArray(), JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR); 250 | } 251 | 252 | public function getIterator(): \ArrayIterator 253 | { 254 | return new \ArrayIterator($this->hits); 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/Contracts/Task.php: -------------------------------------------------------------------------------- 1 | taskUid; 52 | } 53 | 54 | /** 55 | * @return non-empty-string|null 56 | */ 57 | public function getIndexUid(): ?string 58 | { 59 | return $this->indexUid; 60 | } 61 | 62 | public function getStatus(): TaskStatus 63 | { 64 | return $this->status; 65 | } 66 | 67 | public function getType(): TaskType 68 | { 69 | return $this->type; 70 | } 71 | 72 | public function getEnqueuedAt(): \DateTimeImmutable 73 | { 74 | return $this->enqueuedAt; 75 | } 76 | 77 | public function getStartedAt(): ?\DateTimeImmutable 78 | { 79 | return $this->startedAt; 80 | } 81 | 82 | public function getFinishedAt(): ?\DateTimeImmutable 83 | { 84 | return $this->finishedAt; 85 | } 86 | 87 | /** 88 | * @return non-empty-string|null 89 | */ 90 | public function getDuration(): ?string 91 | { 92 | return $this->duration; 93 | } 94 | 95 | public function getCanceledBy(): ?int 96 | { 97 | return $this->canceledBy; 98 | } 99 | 100 | public function getBatchUid(): ?int 101 | { 102 | return $this->batchUid; 103 | } 104 | 105 | public function getDetails(): ?TaskDetails 106 | { 107 | return $this->details; 108 | } 109 | 110 | public function getError(): ?TaskError 111 | { 112 | return $this->error; 113 | } 114 | 115 | public function isFinished(): bool 116 | { 117 | return TaskStatus::Enqueued !== $this->status && TaskStatus::Processing !== $this->status; 118 | } 119 | 120 | public function wait(int $timeoutInMs = 5000, int $intervalInMs = 50): Task 121 | { 122 | if ($this->isFinished()) { 123 | return $this; 124 | } 125 | 126 | if (null !== $this->await) { 127 | return ($this->await)($this->taskUid, $timeoutInMs, $intervalInMs); 128 | } 129 | 130 | throw new LogicException(\sprintf('Cannot wait for task because wait function is not provided.')); 131 | } 132 | 133 | /** 134 | * @param array{ 135 | * taskUid?: int, 136 | * uid?: int, 137 | * indexUid?: non-empty-string, 138 | * status: non-empty-string, 139 | * type: non-empty-string, 140 | * enqueuedAt: non-empty-string, 141 | * startedAt?: non-empty-string|null, 142 | * finishedAt?: non-empty-string|null, 143 | * duration?: non-empty-string|null, 144 | * canceledBy?: int, 145 | * batchUid?: int, 146 | * details?: array|null, 147 | * error?: array|null 148 | * } $data 149 | * @param \Closure(int, int, int): Task|null $await 150 | */ 151 | public static function fromArray(array $data, ?\Closure $await = null): Task 152 | { 153 | $details = $data['details'] ?? null; 154 | $type = TaskType::tryFrom($data['type']) ?? TaskType::Unknown; 155 | 156 | return new self( 157 | $data['taskUid'] ?? $data['uid'], 158 | $data['indexUid'] ?? null, 159 | TaskStatus::tryFrom($data['status']) ?? TaskStatus::Unknown, 160 | $type, 161 | new \DateTimeImmutable($data['enqueuedAt']), 162 | \array_key_exists('startedAt', $data) && null !== $data['startedAt'] ? new \DateTimeImmutable($data['startedAt']) : null, 163 | \array_key_exists('finishedAt', $data) && null !== $data['finishedAt'] ? new \DateTimeImmutable($data['finishedAt']) : null, 164 | $data['duration'] ?? null, 165 | $data['canceledBy'] ?? null, 166 | $data['batchUid'] ?? null, 167 | match ($type) { 168 | TaskType::IndexCreation => null !== $details ? IndexCreationDetails::fromArray($details) : null, 169 | TaskType::IndexUpdate => null !== $details ? IndexUpdateDetails::fromArray($details) : null, 170 | TaskType::IndexDeletion => null !== $details ? IndexDeletionDetails::fromArray($details) : null, 171 | TaskType::IndexSwap => null !== $details ? IndexSwapDetails::fromArray($details) : null, 172 | TaskType::DocumentAdditionOrUpdate => null !== $details ? DocumentAdditionOrUpdateDetails::fromArray($details) : null, 173 | TaskType::DocumentDeletion => null !== $details ? DocumentDeletionDetails::fromArray($details) : null, 174 | TaskType::DocumentEdition => null !== $details ? DocumentEditionDetails::fromArray($details) : null, 175 | TaskType::SettingsUpdate => null !== $details ? SettingsUpdateDetails::fromArray($details) : null, 176 | TaskType::DumpCreation => null !== $details ? DumpCreationDetails::fromArray($details) : null, 177 | TaskType::TaskCancelation => null !== $details ? TaskCancelationDetails::fromArray($details) : null, 178 | TaskType::TaskDeletion => null !== $details ? TaskDeletionDetails::fromArray($details) : null, 179 | // It’s intentional that SnapshotCreation tasks don’t have a details object 180 | // (no SnapshotCreationDetails exists and tests don’t exercise any details) 181 | TaskType::SnapshotCreation => null, 182 | TaskType::Unknown => UnknownTaskDetails::fromArray($details ?? []), 183 | }, 184 | \array_key_exists('error', $data) && null !== $data['error'] ? TaskError::fromArray($data['error']) : null, 185 | $await, 186 | ); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/Contracts/TaskDetails/SettingsUpdateDetails.php: -------------------------------------------------------------------------------- 1 | , 12 | * displayedAttributes?: list, 13 | * distinctAttribute?: string, 14 | * embedders?: non-empty-array, 25 | * response?: array, 26 | * revision?: string, 27 | * searchEmbedder?: array{model: string, source: string}, 28 | * source?: string, 29 | * url?: string 30 | * }>, 31 | * faceting?: array{maxValuesPerFacet: non-negative-int, sortFacetValuesBy: array}|null, 32 | * facetSearch?: bool, 33 | * filterableAttributes?: list, features: array{facetSearch: bool, filter: array{equality: bool, comparison: bool}}}>|null, 34 | * localizedAttributes?: list, attributePatterns: list}>, 35 | * nonSeparatorTokens?: list, 36 | * pagination?: array{maxTotalHits: non-negative-int}, 37 | * prefixSearch?: non-empty-string|null, 38 | * proximityPrecision?: 'byWord'|'byAttribute', 39 | * rankingRules?: list, 40 | * searchableAttributes?: list, 41 | * searchCutoffMs?: non-negative-int, 42 | * separatorTokens?: list, 43 | * sortableAttributes?: list, 44 | * stopWords?: list, 45 | * synonyms?: array>, 46 | * typoTolerance?: array{ 47 | * enabled: bool, 48 | * minWordSizeForTypos: array{oneTypo: int, twoTypos: int}, 49 | * disableOnWords: list, 50 | * disableOnAttributes: list, 51 | * disableOnNumbers: bool 52 | * } 53 | * }> 54 | */ 55 | final class SettingsUpdateDetails implements TaskDetails 56 | { 57 | /** 58 | * @param list|null $dictionary 59 | * @param list|null $displayedAttributes 60 | * @param non-empty-array, 71 | * response?: array, 72 | * revision?: string, 73 | * searchEmbedder?: array{model: string, source: string}, 74 | * source?: string, 75 | * url?: string 76 | * }>|null $embedders 77 | * @param array{maxValuesPerFacet: non-negative-int, sortFacetValuesBy: array}|null $faceting 78 | * @param list, features: array{facetSearch: bool, filter: array{equality: bool, comparison: bool}}}>|null $filterableAttributes 79 | * @param list, attributePatterns: list}>|null $localizedAttributes 80 | * @param list|null $nonSeparatorTokens 81 | * @param array{maxTotalHits: non-negative-int}|null $pagination 82 | * @param 'indexingTime'|'disabled'|null $prefixSearch 83 | * @param 'byWord'|'byAttribute'|null $proximityPrecision 84 | * @param list|null $rankingRules 85 | * @param list|null $searchableAttributes 86 | * @param non-negative-int|null $searchCutoffMs 87 | * @param list $separatorTokens 88 | * @param list|null $sortableAttributes 89 | * @param list|null $stopWords 90 | * @param array>|null $synonyms 91 | * @param array{ 92 | * enabled: bool, 93 | * minWordSizeForTypos: array{oneTypo: int, twoTypos: int}, 94 | * disableOnWords: list, 95 | * disableOnAttributes: list, 96 | * disableOnNumbers: bool 97 | * }|null $typoTolerance 98 | */ 99 | public function __construct( 100 | public readonly ?array $dictionary, 101 | public readonly ?array $displayedAttributes, 102 | public readonly ?string $distinctAttribute, 103 | public readonly ?array $embedders, 104 | public readonly ?array $faceting, 105 | public readonly ?bool $facetSearch, 106 | public readonly ?array $filterableAttributes, 107 | public readonly ?array $localizedAttributes, 108 | public readonly ?array $nonSeparatorTokens, 109 | public readonly ?array $pagination, 110 | public readonly ?string $prefixSearch, 111 | public readonly ?string $proximityPrecision, 112 | public readonly ?array $rankingRules, 113 | public readonly ?array $searchableAttributes, 114 | public readonly ?int $searchCutoffMs, 115 | public readonly ?array $separatorTokens, 116 | public readonly ?array $sortableAttributes, 117 | public readonly ?array $stopWords, 118 | public readonly ?array $synonyms, 119 | public readonly ?array $typoTolerance, 120 | ) { 121 | } 122 | 123 | public static function fromArray(array $data): self 124 | { 125 | return new self( 126 | $data['dictionary'] ?? null, 127 | $data['displayedAttributes'] ?? null, 128 | $data['distinctAttribute'] ?? null, 129 | $data['embedders'] ?? null, 130 | $data['faceting'] ?? null, 131 | $data['facetSearch'] ?? null, 132 | $data['filterableAttributes'] ?? null, 133 | $data['localizedAttributes'] ?? null, 134 | $data['nonSeparatorTokens'] ?? null, 135 | $data['pagination'] ?? null, 136 | $data['prefixSearch'] ?? null, 137 | $data['proximityPrecision'] ?? null, 138 | $data['rankingRules'] ?? null, 139 | $data['searchableAttributes'] ?? null, 140 | $data['searchCutoffMs'] ?? null, 141 | $data['separatorTokens'] ?? null, 142 | $data['sortableAttributes'] ?? null, 143 | $data['stopWords'] ?? null, 144 | $data['synonyms'] ?? null, 145 | $data['typoTolerance'] ?? null, 146 | ); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/Endpoints/Indexes.php: -------------------------------------------------------------------------------- 1 | uid = $uid; 43 | $this->primaryKey = $primaryKey; 44 | $this->createdAt = $createdAt; 45 | $this->updatedAt = $updatedAt; 46 | $this->tasks = new Tasks($http); 47 | 48 | parent::__construct($http); 49 | } 50 | 51 | protected function newInstance(array $attributes): self 52 | { 53 | return new self( 54 | $this->http, 55 | $attributes['uid'], 56 | $attributes['primaryKey'], 57 | null !== $attributes['createdAt'] ? new \DateTimeImmutable($attributes['createdAt']) : null, 58 | null !== $attributes['updatedAt'] ? new \DateTimeImmutable($attributes['updatedAt']) : null, 59 | ); 60 | } 61 | 62 | /** 63 | * @return $this 64 | */ 65 | protected function fill(array $attributes): self 66 | { 67 | $this->uid = $attributes['uid']; 68 | $this->primaryKey = $attributes['primaryKey']; 69 | $this->createdAt = null !== $attributes['createdAt'] ? new \DateTimeImmutable($attributes['createdAt']) : null; 70 | $this->updatedAt = null !== $attributes['updatedAt'] ? new \DateTimeImmutable($attributes['updatedAt']) : null; 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * @throws \Exception|ApiException 77 | */ 78 | public function create(string $uid, array $options = []): Task 79 | { 80 | $options['uid'] = $uid; 81 | 82 | return Task::fromArray($this->http->post(self::PATH, $options), partial(\Closure::fromCallable([Tasks::class, 'waitTask']), $this->http)); 83 | } 84 | 85 | public function all(?IndexesQuery $options = null): IndexesResults 86 | { 87 | $indexes = []; 88 | $query = isset($options) ? $options->toArray() : []; 89 | $response = $this->allRaw($query); 90 | 91 | foreach ($response['results'] as $index) { 92 | $indexes[] = $this->newInstance($index); 93 | } 94 | 95 | $response['results'] = $indexes; 96 | 97 | return new IndexesResults($response); 98 | } 99 | 100 | public function allRaw(array $options = []): array 101 | { 102 | return $this->http->get(self::PATH, $options); 103 | } 104 | 105 | public function getPrimaryKey(): ?string 106 | { 107 | return $this->primaryKey; 108 | } 109 | 110 | public function fetchPrimaryKey(): ?string 111 | { 112 | return $this->fetchInfo()->getPrimaryKey(); 113 | } 114 | 115 | public function getUid(): ?string 116 | { 117 | return $this->uid; 118 | } 119 | 120 | public function getCreatedAt(): ?\DateTimeInterface 121 | { 122 | return $this->createdAt; 123 | } 124 | 125 | public function getUpdatedAt(): ?\DateTimeInterface 126 | { 127 | return $this->updatedAt; 128 | } 129 | 130 | public function fetchRawInfo(): ?array 131 | { 132 | return $this->http->get(self::PATH.'/'.$this->uid); 133 | } 134 | 135 | public function fetchInfo(): self 136 | { 137 | $response = $this->fetchRawInfo(); 138 | 139 | return $this->fill($response); 140 | } 141 | 142 | public function update(array $body): Task 143 | { 144 | return Task::fromArray($this->http->patch(self::PATH.'/'.$this->uid, $body), partial(Tasks::waitTask(...), $this->http)); 145 | } 146 | 147 | public function delete(): Task 148 | { 149 | $response = $this->http->delete(self::PATH.'/'.$this->uid); 150 | \assert(null !== $response); 151 | 152 | return Task::fromArray($response, partial(Tasks::waitTask(...), $this->http)); 153 | } 154 | 155 | /** 156 | * @param array $indexes 157 | */ 158 | public function swapIndexes(array $indexes): Task 159 | { 160 | return Task::fromArray($this->http->post('/swap-indexes', $indexes), partial(Tasks::waitTask(...), $this->http)); 161 | } 162 | 163 | // Tasks 164 | 165 | public function getTask(int $uid): Task 166 | { 167 | return Task::fromArray($this->http->get('/tasks/'.$uid), partial(Tasks::waitTask(...), $this->http)); 168 | } 169 | 170 | public function getTasks(?TasksQuery $options = null): TasksResults 171 | { 172 | $options = $options ?? new TasksQuery(); 173 | 174 | if ([] !== $options->getIndexUids()) { 175 | $options->setIndexUids([$this->uid, ...$options->getIndexUids()]); 176 | } else { 177 | $options->setIndexUids([$this->uid]); 178 | } 179 | 180 | $response = $this->http->get('/tasks', $options->toArray()); 181 | 182 | return new TasksResults($response); 183 | } 184 | 185 | // Search 186 | 187 | /** 188 | * @phpstan-return ($options is array{raw: true|non-falsy-string|positive-int} ? array : SearchResult) 189 | */ 190 | public function search(?string $query, array $searchParams = [], array $options = []): SearchResult|array 191 | { 192 | $result = $this->rawSearch($query, $searchParams); 193 | 194 | if (\array_key_exists('raw', $options) && $options['raw']) { 195 | return $result; 196 | } 197 | 198 | $searchResult = new SearchResult($result); 199 | $searchResult->applyOptions($options); 200 | 201 | return $searchResult; 202 | } 203 | 204 | public function rawSearch(?string $query, array $searchParams = []): array 205 | { 206 | $parameters = array_merge( 207 | ['q' => $query], 208 | $searchParams 209 | ); 210 | 211 | $result = $this->http->post(self::PATH.'/'.$this->uid.'/search', $parameters); 212 | 213 | // patch to prevent breaking in laravel/scout getTotalCount method, 214 | // affects only Meilisearch >= v0.28.0. 215 | if (isset($result['estimatedTotalHits'])) { 216 | $result['nbHits'] = $result['estimatedTotalHits']; 217 | } 218 | 219 | return $result; 220 | } 221 | 222 | public function searchSimilarDocuments(SimilarDocumentsQuery $parameters): SimilarDocumentsSearchResult 223 | { 224 | $result = $this->http->post(self::PATH.'/'.$this->uid.'/similar', $parameters->toArray()); 225 | 226 | return new SimilarDocumentsSearchResult($result); 227 | } 228 | 229 | // Facet Search 230 | 231 | public function facetSearch(FacetSearchQuery $params): FacetSearchResult 232 | { 233 | $response = $this->http->post(self::PATH.'/'.$this->uid.'/facet-search', $params->toArray()); 234 | 235 | return new FacetSearchResult($response); 236 | } 237 | 238 | // Stats 239 | 240 | public function stats(): array 241 | { 242 | return $this->http->get(self::PATH.'/'.$this->uid.'/stats'); 243 | } 244 | 245 | // Settings - Global 246 | 247 | public function getSettings(): array 248 | { 249 | return (new Settings($this->http->get(self::PATH.'/'.$this->uid.'/settings'))) 250 | ->getIterator()->getArrayCopy(); 251 | } 252 | 253 | public function updateSettings($settings): Task 254 | { 255 | return Task::fromArray($this->http->patch(self::PATH.'/'.$this->uid.'/settings', $settings), partial(Tasks::waitTask(...), $this->http)); 256 | } 257 | 258 | public function resetSettings(): Task 259 | { 260 | return Task::fromArray($this->http->delete(self::PATH.'/'.$this->uid.'/settings'), partial(Tasks::waitTask(...), $this->http)); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/Http/Client.php: -------------------------------------------------------------------------------- 1 | 31 | */ 32 | private array $headers; 33 | /** 34 | * @var non-empty-string 35 | */ 36 | private string $baseUrl; 37 | private Json $json; 38 | 39 | /** 40 | * @param non-empty-string $url 41 | * @param array $clientAgents 42 | */ 43 | public function __construct( 44 | string $url, 45 | ?string $apiKey = null, 46 | ?ClientInterface $httpClient = null, 47 | ?RequestFactoryInterface $reqFactory = null, 48 | array $clientAgents = [], 49 | ?StreamFactoryInterface $streamFactory = null, 50 | ) { 51 | $this->baseUrl = $url; 52 | $this->http = $httpClient ?? Psr18ClientDiscovery::find(); 53 | $this->requestFactory = $reqFactory ?? Psr17FactoryDiscovery::findRequestFactory(); 54 | $this->streamFactory = $streamFactory ?? Psr17FactoryDiscovery::findStreamFactory(); 55 | $this->headers = [ 56 | 'User-Agent' => implode(';', array_merge($clientAgents, [Meilisearch::qualifiedVersion()])), 57 | ]; 58 | if (null !== $apiKey && '' !== $apiKey) { 59 | $this->headers['Authorization'] = \sprintf('Bearer %s', $apiKey); 60 | } 61 | $this->json = new Json(); 62 | } 63 | 64 | /** 65 | * @throws ClientExceptionInterface 66 | * @throws ApiException 67 | * @throws CommunicationException 68 | */ 69 | public function get(string $path, array $query = []): mixed 70 | { 71 | $request = $this->requestFactory->createRequest( 72 | 'GET', 73 | $this->baseUrl.$path.$this->buildQueryString($query) 74 | ); 75 | 76 | return $this->execute($request); 77 | } 78 | 79 | /** 80 | * @param non-empty-string|null $contentType 81 | * 82 | * @throws ApiException 83 | * @throws ClientExceptionInterface 84 | * @throws CommunicationException 85 | * @throws \JsonException 86 | */ 87 | public function post(string $path, mixed $body = null, array $query = [], ?string $contentType = null): mixed 88 | { 89 | if (null === $contentType) { 90 | $body = $this->json->serialize($body); 91 | } 92 | $request = $this->requestFactory->createRequest( 93 | 'POST', 94 | $this->baseUrl.$path.$this->buildQueryString($query) 95 | )->withBody($this->streamFactory->createStream($body)); 96 | 97 | return $this->execute($request, ['Content-type' => $contentType ?? 'application/json']); 98 | } 99 | 100 | /** 101 | * @param non-empty-string|null $contentType 102 | * 103 | * @throws ApiException 104 | * @throws ClientExceptionInterface 105 | * @throws CommunicationException 106 | * @throws \JsonException 107 | */ 108 | public function put(string $path, mixed $body = null, array $query = [], ?string $contentType = null): mixed 109 | { 110 | if (null === $contentType) { 111 | $body = $this->json->serialize($body); 112 | } 113 | $request = $this->requestFactory->createRequest( 114 | 'PUT', 115 | $this->baseUrl.$path.$this->buildQueryString($query) 116 | )->withBody($this->streamFactory->createStream($body)); 117 | 118 | return $this->execute($request, ['Content-type' => $contentType ?? 'application/json']); 119 | } 120 | 121 | public function patch(string $path, mixed $body = null, array $query = []): mixed 122 | { 123 | $request = $this->requestFactory->createRequest( 124 | 'PATCH', 125 | $this->baseUrl.$path.$this->buildQueryString($query) 126 | )->withBody($this->streamFactory->createStream($this->json->serialize($body))); 127 | 128 | return $this->execute($request, ['Content-type' => 'application/json']); 129 | } 130 | 131 | public function delete(string $path, array $query = []): mixed 132 | { 133 | $request = $this->requestFactory->createRequest( 134 | 'DELETE', 135 | $this->baseUrl.$path.$this->buildQueryString($query) 136 | ); 137 | 138 | return $this->execute($request); 139 | } 140 | 141 | /** 142 | * @throws ApiException 143 | * @throws ClientExceptionInterface 144 | * @throws CommunicationException 145 | * @throws \JsonException 146 | */ 147 | public function postStream(string $path, mixed $body = null, array $query = []): StreamInterface 148 | { 149 | $request = $this->requestFactory->createRequest( 150 | 'POST', 151 | $this->baseUrl.$path.$this->buildQueryString($query) 152 | )->withBody($this->streamFactory->createStream($this->json->serialize($body))); 153 | 154 | return $this->executeStream($request, ['Content-type' => 'application/json']); 155 | } 156 | 157 | /** 158 | * @param array $headers 159 | * 160 | * @throws ApiException 161 | * @throws ClientExceptionInterface 162 | * @throws CommunicationException 163 | */ 164 | private function execute(RequestInterface $request, array $headers = []) 165 | { 166 | foreach (array_merge($this->headers, $headers) as $header => $value) { 167 | $request = $request->withAddedHeader($header, $value); 168 | } 169 | 170 | try { 171 | return $this->parseResponse($this->http->sendRequest($request)); 172 | } catch (NetworkExceptionInterface $e) { 173 | throw new CommunicationException($e->getMessage(), $e->getCode(), $e); 174 | } 175 | } 176 | 177 | /** 178 | * @param array $headers 179 | * 180 | * @throws ApiException 181 | * @throws ClientExceptionInterface 182 | * @throws CommunicationException 183 | */ 184 | private function executeStream(RequestInterface $request, array $headers = []): StreamInterface 185 | { 186 | foreach (array_merge($this->headers, $headers) as $header => $value) { 187 | $request = $request->withAddedHeader($header, $value); 188 | } 189 | 190 | try { 191 | $response = $this->http->sendRequest($request); 192 | 193 | if ($response->getStatusCode() >= 300) { 194 | $bodyContent = (string) $response->getBody(); 195 | 196 | // Try to parse as JSON for structured errors, fall back to raw content 197 | if ($this->isJSONResponse($response->getHeader('content-type'))) { 198 | try { 199 | $body = $this->json->unserialize($bodyContent) ?? $response->getReasonPhrase(); 200 | } catch (\JsonException $e) { 201 | $body = '' !== $bodyContent ? $bodyContent : $response->getReasonPhrase(); 202 | } 203 | } else { 204 | $body = '' !== $bodyContent ? $bodyContent : $response->getReasonPhrase(); 205 | } 206 | 207 | throw new ApiException($response, $body); 208 | } 209 | 210 | return $response->getBody(); 211 | } catch (NetworkExceptionInterface $e) { 212 | throw new CommunicationException($e->getMessage(), $e->getCode(), $e); 213 | } 214 | } 215 | 216 | private function buildQueryString(array $queryParams = []): string 217 | { 218 | foreach ($queryParams as $key => $value) { 219 | if (\is_bool($value)) { 220 | $queryParams[$key] = $value ? 'true' : 'false'; 221 | } 222 | if (\is_array($value) && array_is_list($value)) { 223 | $queryParams[$key] = implode(',', $value); 224 | } 225 | } 226 | 227 | return [] !== $queryParams ? '?'.http_build_query($queryParams) : ''; 228 | } 229 | 230 | /** 231 | * @throws ApiException 232 | * @throws InvalidResponseBodyException 233 | * @throws \JsonException 234 | */ 235 | private function parseResponse(ResponseInterface $response) 236 | { 237 | if (204 === $response->getStatusCode()) { 238 | return null; 239 | } 240 | 241 | if (!$this->isJSONResponse($response->getHeader('content-type'))) { 242 | throw new InvalidResponseBodyException($response, (string) $response->getBody()); 243 | } 244 | 245 | if ($response->getStatusCode() >= 300) { 246 | $body = $this->json->unserialize((string) $response->getBody()) ?? $response->getReasonPhrase(); 247 | 248 | throw new ApiException($response, $body); 249 | } 250 | 251 | return $this->json->unserialize((string) $response->getBody()); 252 | } 253 | 254 | /** 255 | * Checks if any of the header values indicate a JSON response. 256 | * 257 | * @param array $headerValues the array of header values to check 258 | * 259 | * @return bool true if any header value contains 'application/json', otherwise false 260 | */ 261 | private function isJSONResponse(array $headerValues): bool 262 | { 263 | return [] !== array_filter($headerValues, static fn (string $v) => str_contains($v, 'application/json')); 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/Endpoints/Delegates/HandlesDocuments.php: -------------------------------------------------------------------------------- 1 | implode(',', $fields)] : []; 24 | 25 | return $this->http->get(self::PATH.'/'.$this->uid.'/documents/'.$documentId, $query); 26 | } 27 | 28 | public function getDocuments(?DocumentsQuery $options = null): DocumentsResults 29 | { 30 | try { 31 | $options = $options ?? new DocumentsQuery(); 32 | $query = $options->toArray(); 33 | 34 | if ($options->hasFilter()) { 35 | $response = $this->http->post(self::PATH.'/'.$this->uid.'/documents/fetch', $query); 36 | } else { 37 | $response = $this->http->get(self::PATH.'/'.$this->uid.'/documents', $query); 38 | } 39 | 40 | return new DocumentsResults($response); 41 | } catch (\Exception $e) { 42 | throw ApiException::rethrowWithHint($e, __FUNCTION__); 43 | } 44 | } 45 | 46 | public function addDocuments(array $documents, ?string $primaryKey = null): Task 47 | { 48 | return Task::fromArray($this->http->post(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey]), partial(Tasks::waitTask(...), $this->http)); 49 | } 50 | 51 | public function addDocumentsJson(string $documents, ?string $primaryKey = null): Task 52 | { 53 | return Task::fromArray($this->http->post(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey], 'application/json'), partial(Tasks::waitTask(...), $this->http)); 54 | } 55 | 56 | public function addDocumentsCsv(string $documents, ?string $primaryKey = null, ?string $delimiter = null): Task 57 | { 58 | return Task::fromArray($this->http->post(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey, 'csvDelimiter' => $delimiter], 'text/csv'), partial(Tasks::waitTask(...), $this->http)); 59 | } 60 | 61 | public function addDocumentsNdjson(string $documents, ?string $primaryKey = null): Task 62 | { 63 | return Task::fromArray($this->http->post(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey], 'application/x-ndjson'), partial(Tasks::waitTask(...), $this->http)); 64 | } 65 | 66 | /** 67 | * @return list 68 | */ 69 | public function addDocumentsInBatches(array $documents, ?int $batchSize = 1000, ?string $primaryKey = null): array 70 | { 71 | $promises = []; 72 | 73 | foreach (self::batch($documents, $batchSize) as $batch) { 74 | $promises[] = $this->addDocuments($batch, $primaryKey); 75 | } 76 | 77 | return $promises; 78 | } 79 | 80 | /** 81 | * @return list 82 | */ 83 | public function addDocumentsCsvInBatches(string $documents, ?int $batchSize = 1000, ?string $primaryKey = null, ?string $delimiter = null): array 84 | { 85 | $promises = []; 86 | 87 | foreach (self::batchCsvString($documents, $batchSize) as $batch) { 88 | $promises[] = $this->addDocumentsCsv($batch, $primaryKey, $delimiter); 89 | } 90 | 91 | return $promises; 92 | } 93 | 94 | /** 95 | * @return list 96 | */ 97 | public function addDocumentsNdjsonInBatches(string $documents, ?int $batchSize = 1000, ?string $primaryKey = null): array 98 | { 99 | $promises = []; 100 | 101 | foreach (self::batchNdjsonString($documents, $batchSize) as $batch) { 102 | $promises[] = $this->addDocumentsNdjson($batch, $primaryKey); 103 | } 104 | 105 | return $promises; 106 | } 107 | 108 | public function updateDocuments(array $documents, ?string $primaryKey = null): Task 109 | { 110 | return Task::fromArray($this->http->put(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey]), partial(Tasks::waitTask(...), $this->http)); 111 | } 112 | 113 | public function updateDocumentsJson(string $documents, ?string $primaryKey = null): Task 114 | { 115 | return Task::fromArray($this->http->put(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey], 'application/json'), partial(Tasks::waitTask(...), $this->http)); 116 | } 117 | 118 | public function updateDocumentsCsv(string $documents, ?string $primaryKey = null, ?string $delimiter = null): Task 119 | { 120 | return Task::fromArray($this->http->put(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey, 'csvDelimiter' => $delimiter], 'text/csv'), partial(Tasks::waitTask(...), $this->http)); 121 | } 122 | 123 | public function updateDocumentsNdjson(string $documents, ?string $primaryKey = null): Task 124 | { 125 | return Task::fromArray($this->http->put(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey], 'application/x-ndjson'), partial(Tasks::waitTask(...), $this->http)); 126 | } 127 | 128 | /** 129 | * @return list 130 | */ 131 | public function updateDocumentsInBatches(array $documents, ?int $batchSize = 1000, ?string $primaryKey = null): array 132 | { 133 | $promises = []; 134 | 135 | foreach (self::batch($documents, $batchSize) as $batch) { 136 | $promises[] = $this->updateDocuments($batch, $primaryKey); 137 | } 138 | 139 | return $promises; 140 | } 141 | 142 | /** 143 | * @return list 144 | */ 145 | public function updateDocumentsCsvInBatches(string $documents, ?int $batchSize = 1000, ?string $primaryKey = null, ?string $delimiter = null): array 146 | { 147 | $promises = []; 148 | 149 | foreach (self::batchCsvString($documents, $batchSize) as $batch) { 150 | $promises[] = $this->updateDocumentsCsv($batch, $primaryKey, $delimiter); 151 | } 152 | 153 | return $promises; 154 | } 155 | 156 | /** 157 | * @return list 158 | */ 159 | public function updateDocumentsNdjsonInBatches(string $documents, ?int $batchSize = 1000, ?string $primaryKey = null): array 160 | { 161 | $promises = []; 162 | 163 | foreach (self::batchNdjsonString($documents, $batchSize) as $batch) { 164 | $promises[] = $this->updateDocumentsNdjson($batch, $primaryKey); 165 | } 166 | 167 | return $promises; 168 | } 169 | 170 | /** 171 | * This is an EXPERIMENTAL feature, which may break without a major version. 172 | * It's available after Meilisearch v1.10. 173 | * 174 | * More info about the feature: https://github.com/orgs/meilisearch/discussions/762 175 | * More info about experimental features in general: https://www.meilisearch.com/docs/reference/api/experimental-features 176 | * 177 | * @param non-empty-string $function 178 | * @param array{filter?: non-empty-string|list|null, context?: array} $options 179 | */ 180 | public function updateDocumentsByFunction(string $function, array $options = []): Task 181 | { 182 | return Task::fromArray($this->http->post(self::PATH.'/'.$this->uid.'/documents/edit', array_merge(['function' => $function], $options)), partial(Tasks::waitTask(...), $this->http)); 183 | } 184 | 185 | public function deleteAllDocuments(): Task 186 | { 187 | return Task::fromArray($this->http->delete(self::PATH.'/'.$this->uid.'/documents'), partial(Tasks::waitTask(...), $this->http)); 188 | } 189 | 190 | public function deleteDocument(string|int $documentId): Task 191 | { 192 | return Task::fromArray($this->http->delete(self::PATH.'/'.$this->uid.'/documents/'.$documentId), partial(Tasks::waitTask(...), $this->http)); 193 | } 194 | 195 | public function deleteDocuments(array $options): Task 196 | { 197 | try { 198 | if (\array_key_exists('filter', $options) && $options['filter']) { 199 | return Task::fromArray($this->http->post(self::PATH.'/'.$this->uid.'/documents/delete', $options), partial(Tasks::waitTask(...), $this->http)); 200 | } 201 | 202 | // backwards compatibility: 203 | // expect to be a array to send alongside as $documents_ids. 204 | return Task::fromArray($this->http->post(self::PATH.'/'.$this->uid.'/documents/delete-batch', $options), partial(Tasks::waitTask(...), $this->http)); 205 | } catch (InvalidResponseBodyException $e) { 206 | throw ApiException::rethrowWithHint($e, __FUNCTION__); 207 | } 208 | } 209 | 210 | private static function batchCsvString(string $documents, int $batchSize): \Generator 211 | { 212 | $parsedDocuments = preg_split("/\r\n|\n|\r/", trim($documents)); 213 | $csvHeader = $parsedDocuments[0]; 214 | array_shift($parsedDocuments); 215 | $batches = array_chunk($parsedDocuments, $batchSize); 216 | 217 | /** @var array $batch */ 218 | foreach ($batches as $batch) { 219 | array_unshift($batch, $csvHeader); 220 | $batch = implode("\n", $batch); 221 | 222 | yield $batch; 223 | } 224 | } 225 | 226 | private static function batchNdjsonString(string $documents, int $batchSize): \Generator 227 | { 228 | $parsedDocuments = preg_split("/\r\n|\n|\r/", trim($documents)); 229 | $batches = array_chunk($parsedDocuments, $batchSize); 230 | 231 | /** @var array $batch */ 232 | foreach ($batches as $batch) { 233 | $batch = implode("\n", $batch); 234 | 235 | yield $batch; 236 | } 237 | } 238 | 239 | private static function batch(array $documents, int $batchSize): \Generator 240 | { 241 | $batches = array_chunk($documents, $batchSize); 242 | 243 | foreach ($batches as $batch) { 244 | yield $batch; 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Meilisearch-PHP 3 |

4 | 5 |

Meilisearch PHP

6 | 7 |

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

16 | 17 |

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

23 | 24 |

⚡ The Meilisearch API client written for PHP 🐘

25 | 26 | **Meilisearch PHP** is the Meilisearch API client for PHP developers. 27 | 28 | **Meilisearch** is an open-source search engine. [Learn more about Meilisearch.](https://github.com/meilisearch/Meilisearch) 29 | 30 | ## Table of Contents 31 | 32 | - [📖 Documentation](#-documentation) 33 | - [🔧 Installation](#-installation) 34 | - [🚀 Getting started](#-getting-started) 35 | - [🤖 Compatibility with Meilisearch](#-compatibility-with-meilisearch) 36 | - [💡 Learn more](#-learn-more) 37 | - [🧰 HTTP Client Compatibilities](#-http-client-compatibilities) 38 | - [Customize your HTTP Client](#customize-your-http-client) 39 | - [⚙️ Contributing](#️-contributing) 40 | 41 | ## 📖 Documentation 42 | 43 | 44 | 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). 45 | 46 | ## 🔧 Installation 47 | 48 | To get started, simply require the project using [Composer](https://getcomposer.org/).
49 | 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).
50 | 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). 51 | 52 | **If you don't know which HTTP client to use, we recommend using Guzzle 7**: 53 | 54 | ```bash 55 | composer require meilisearch/meilisearch-php guzzlehttp/guzzle http-interop/http-factory-guzzle:^1.0 56 | ``` 57 | 58 | Here is an example of installation with the `symfony/http-client`: 59 | 60 | ```bash 61 | composer require meilisearch/meilisearch-php symfony/http-client nyholm/psr7:^1.0 62 | ``` 63 | 64 | 💡 *More HTTP client installations compatible with this package can be found [in this section](#-http-client-compatibilities).* 65 | 66 | ### Run Meilisearch 67 | 68 | ⚡️ **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). 69 | 70 | 🪨 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. 71 | 72 | ## 🚀 Getting started 73 | 74 | #### Add documents 75 | 76 | ```php 77 | index('movies'); 87 | 88 | $documents = [ 89 | ['id' => 1, 'title' => 'Carol', 'genres' => ['Romance, Drama']], 90 | ['id' => 2, 'title' => 'Wonder Woman', 'genres' => ['Action, Adventure']], 91 | ['id' => 3, 'title' => 'Life of Pi', 'genres' => ['Adventure, Drama']], 92 | ['id' => 4, 'title' => 'Mad Max: Fury Road', 'genres' => ['Adventure, Science Fiction']], 93 | ['id' => 5, 'title' => 'Moana', 'genres' => ['Fantasy, Action']], 94 | ['id' => 6, 'title' => 'Philadelphia', 'genres' => ['Drama']], 95 | ]; 96 | 97 | # If the index 'movies' does not exist, Meilisearch creates it when you first add the documents. 98 | $index->addDocuments($documents); // => { "uid": 0 } 99 | ``` 100 | 101 | 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). 102 | 103 | #### Basic Search 104 | 105 | ```php 106 | // Meilisearch is typo-tolerant: 107 | $hits = $index->search('wondre woman')->getHits(); 108 | print_r($hits); 109 | ``` 110 | 111 | Output: 112 | 113 | ```php 114 | Array 115 | ( 116 | [0] => Array 117 | ( 118 | [id] => 2 119 | [title] => Wonder Woman 120 | [genres] => Array 121 | ( 122 | [0] => Action, Adventure 123 | ) 124 | ) 125 | ) 126 | ``` 127 | 128 | #### Custom Search 129 | 130 | All the supported options are described in the [search parameters](https://www.meilisearch.com/docs/reference/api/search#search-parameters) section of the documentation. 131 | 132 | 💡 **More about the `search()` method in [the Wiki](https://github.com/meilisearch/meilisearch-php/wiki/Search).** 133 | 134 | ```php 135 | $index->search( 136 | 'phil', 137 | [ 138 | 'attributesToHighlight' => ['*'], 139 | ] 140 | )->getRaw(); // Return in Array format 141 | ``` 142 | 143 | JSON output: 144 | 145 | ```json 146 | { 147 | "hits": [ 148 | { 149 | "id": 6, 150 | "title": "Philadelphia", 151 | "genre": ["Drama"], 152 | "_formatted": { 153 | "id": 6, 154 | "title": "Philadelphia", 155 | "genre": ["Drama"] 156 | } 157 | } 158 | ], 159 | "offset": 0, 160 | "limit": 20, 161 | "processingTimeMs": 0, 162 | "query": "phil" 163 | } 164 | ``` 165 | #### Custom Search With Filters 166 | 167 | If you want to enable filtering, you must add your attributes to the `filterableAttributes` index setting. 168 | 169 | ```php 170 | $index->updateFilterableAttributes([ 171 | 'id', 172 | 'genres' 173 | ]); 174 | ``` 175 | 176 | You only need to perform this operation once. 177 | 178 | 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)). 179 | 180 | Then, you can perform the search: 181 | 182 | ```php 183 | $index->search( 184 | 'wonder', 185 | [ 186 | 'filter' => ['id > 1 AND genres = Action'] 187 | ] 188 | ); 189 | ``` 190 | 191 | ```json 192 | { 193 | "hits": [ 194 | { 195 | "id": 2, 196 | "title": "Wonder Woman", 197 | "genres": ["Action","Adventure"] 198 | } 199 | ], 200 | "offset": 0, 201 | "limit": 20, 202 | "estimatedTotalHits": 1, 203 | "processingTimeMs": 0, 204 | "query": "wonder" 205 | } 206 | ``` 207 | 208 | ## 🤖 Compatibility with Meilisearch 209 | 210 | 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. 211 | 212 | ## 💡 Learn more 213 | 214 | The following sections in our main documentation website may interest you: 215 | 216 | - **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). 217 | - **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). 218 | - **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). 219 | - **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). 220 | 221 | ## 🧰 HTTP Client Compatibilities 222 | 223 | 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.
224 | 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). 225 | 226 | If you want to use this `meilisearch-php`: 227 | 228 | - with `guzzlehttp/guzzle` (Guzzle 7), run: 229 | 230 | ```bash 231 | composer require meilisearch/meilisearch-php guzzlehttp/guzzle http-interop/http-factory-guzzle:^1.0 232 | ``` 233 | 234 | - with `php-http/guzzle6-adapter` (Guzzle < 7), run: 235 | 236 | ```bash 237 | composer require meilisearch/meilisearch-php php-http/guzzle6-adapter:^2.0 http-interop/http-factory-guzzle:^1.0 238 | ``` 239 | 240 | - with `symfony/http-client`, run: 241 | 242 | ```bash 243 | composer require meilisearch/meilisearch-php symfony/http-client nyholm/psr7:^1.0 244 | ``` 245 | 246 | - with `php-http/curl-client`, run: 247 | 248 | ```bash 249 | composer require meilisearch/meilisearch-php php-http/curl-client nyholm/psr7:^1.0 250 | ``` 251 | 252 | - with `kriswallsmith/buzz`, run: 253 | 254 | ```bash 255 | composer require meilisearch/meilisearch-php kriswallsmith/buzz nyholm/psr7:^1.0 256 | ``` 257 | 258 | ### Customize your HTTP Client 259 | 260 | For some reason, you might want to pass a custom configuration to your own HTTP client.
261 | Make sure you have a [PSR-18](https://www.php-fig.org/psr/psr-18/) compatible client when you initialize the Meilisearch client. 262 | 263 | Following the example in the [Getting started](#-getting-started) section, with the Guzzle HTTP client: 264 | 265 | ```php 266 | new Client('http://127.0.0.1:7700', 'masterKey', new GuzzleHttpClient(['timeout' => 2])); 267 | ``` 268 | 269 | ## ⚙️ Contributing 270 | 271 | Any new contribution is more than welcome in this project! 272 | 273 | If you want to know more about the development workflow or want to contribute, please visit our [contributing guidelines](/CONTRIBUTING.md) for detailed instructions! 274 | 275 |
276 | 277 | **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. 278 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------