Ver código fonte

added meilisearch api package

joe 3 anos atrás
pai
commit
5a2e9e004d
100 arquivos alterados com 8827 adições e 181 exclusões
  1. 4 1
      composer.json
  2. 784 85
      composer.lock
  3. 6 0
      config/meilisearch.php
  4. 2 1
      tw/command/Maintain.php
  5. 2 0
      vendor/clue/stream-filter/.github/FUNDING.yml
  6. 86 0
      vendor/clue/stream-filter/CHANGELOG.md
  7. 21 0
      vendor/clue/stream-filter/LICENSE
  8. 326 0
      vendor/clue/stream-filter/README.md
  9. 26 0
      vendor/clue/stream-filter/composer.json
  10. 120 0
      vendor/clue/stream-filter/src/CallbackFilter.php
  11. 327 0
      vendor/clue/stream-filter/src/functions.php
  12. 6 0
      vendor/clue/stream-filter/src/functions_include.php
  13. 6 0
      vendor/composer/autoload_classmap.php
  14. 7 2
      vendor/composer/autoload_files.php
  15. 14 1
      vendor/composer/autoload_psr4.php
  16. 84 3
      vendor/composer/autoload_static.php
  17. 813 84
      vendor/composer/installed.json
  18. 176 2
      vendor/composer/installed.php
  19. 2 2
      vendor/composer/platform_check.php
  20. 21 0
      vendor/http-interop/http-factory-guzzle/LICENSE
  21. 3 0
      vendor/http-interop/http-factory-guzzle/README.md
  22. 33 0
      vendor/http-interop/http-factory-guzzle/composer.json
  23. 15 0
      vendor/http-interop/http-factory-guzzle/src/RequestFactory.php
  24. 15 0
      vendor/http-interop/http-factory-guzzle/src/ResponseFactory.php
  25. 24 0
      vendor/http-interop/http-factory-guzzle/src/ServerRequestFactory.php
  26. 43 0
      vendor/http-interop/http-factory-guzzle/src/StreamFactory.php
  27. 25 0
      vendor/http-interop/http-factory-guzzle/src/UploadedFileFactory.php
  28. 15 0
      vendor/http-interop/http-factory-guzzle/src/UriFactory.php
  29. 309 0
      vendor/meilisearch/meilisearch-php/.code-samples.meilisearch.yaml
  30. 20 0
      vendor/meilisearch/meilisearch-php/.editorconfig
  31. 27 0
      vendor/meilisearch/meilisearch-php/.github/release-draft-template.yml
  32. 66 0
      vendor/meilisearch/meilisearch-php/.github/workflows/pre-release-tests.yml
  33. 16 0
      vendor/meilisearch/meilisearch-php/.github/workflows/release-drafter.yml
  34. 82 0
      vendor/meilisearch/meilisearch-php/.github/workflows/tests.yml
  35. 3 0
      vendor/meilisearch/meilisearch-php/.gitignore
  36. 19 0
      vendor/meilisearch/meilisearch-php/.php_cs.dist
  37. 111 0
      vendor/meilisearch/meilisearch-php/CONTRIBUTING.md
  38. 21 0
      vendor/meilisearch/meilisearch-php/LICENSE
  39. 232 0
      vendor/meilisearch/meilisearch-php/README.md
  40. 9 0
      vendor/meilisearch/meilisearch-php/bors.toml
  41. 52 0
      vendor/meilisearch/meilisearch-php/composer.json
  42. 14 0
      vendor/meilisearch/meilisearch-php/phpunit.xml
  43. 7 0
      vendor/meilisearch/meilisearch-php/scripts/tests.sh
  44. 66 0
      vendor/meilisearch/meilisearch-php/src/Client.php
  45. 23 0
      vendor/meilisearch/meilisearch-php/src/Contracts/Endpoint.php
  46. 18 0
      vendor/meilisearch/meilisearch-php/src/Contracts/Http.php
  47. 73 0
      vendor/meilisearch/meilisearch-php/src/Delegates/HandlesIndex.php
  48. 52 0
      vendor/meilisearch/meilisearch-php/src/Delegates/HandlesSystem.php
  49. 64 0
      vendor/meilisearch/meilisearch-php/src/Endpoints/Delegates/HandlesDocuments.php
  50. 23 0
      vendor/meilisearch/meilisearch-php/src/Endpoints/Delegates/HandlesDumps.php
  51. 132 0
      vendor/meilisearch/meilisearch-php/src/Endpoints/Delegates/HandlesSettings.php
  52. 22 0
      vendor/meilisearch/meilisearch-php/src/Endpoints/Dumps.php
  53. 12 0
      vendor/meilisearch/meilisearch-php/src/Endpoints/Health.php
  54. 198 0
      vendor/meilisearch/meilisearch-php/src/Endpoints/Indexes.php
  55. 12 0
      vendor/meilisearch/meilisearch-php/src/Endpoints/Keys.php
  56. 12 0
      vendor/meilisearch/meilisearch-php/src/Endpoints/Stats.php
  57. 12 0
      vendor/meilisearch/meilisearch-php/src/Endpoints/Version.php
  58. 88 0
      vendor/meilisearch/meilisearch-php/src/Exceptions/ApiException.php
  59. 15 0
      vendor/meilisearch/meilisearch-php/src/Exceptions/CommunicationException.php
  60. 28 0
      vendor/meilisearch/meilisearch-php/src/Exceptions/InvalidArgumentException.php
  61. 32 0
      vendor/meilisearch/meilisearch-php/src/Exceptions/TimeOutException.php
  62. 195 0
      vendor/meilisearch/meilisearch-php/src/Http/Client.php
  63. 10 0
      vendor/meilisearch/meilisearch-php/src/MeiliSearch.php
  64. 248 0
      vendor/meilisearch/meilisearch-php/src/Search/SearchResult.php
  65. 310 0
      vendor/meilisearch/meilisearch-php/tests/Endpoints/ClientTest.php
  66. 318 0
      vendor/meilisearch/meilisearch-php/tests/Endpoints/DocumentsTest.php
  67. 32 0
      vendor/meilisearch/meilisearch-php/tests/Endpoints/DumpTest.php
  68. 215 0
      vendor/meilisearch/meilisearch-php/tests/Endpoints/IndexTest.php
  69. 74 0
      vendor/meilisearch/meilisearch-php/tests/Endpoints/KeysAndPermissionsTest.php
  70. 605 0
      vendor/meilisearch/meilisearch-php/tests/Endpoints/SearchTest.php
  71. 63 0
      vendor/meilisearch/meilisearch-php/tests/Endpoints/UpdatesTest.php
  72. 36 0
      vendor/meilisearch/meilisearch-php/tests/Exceptions/ApiExceptionTest.php
  73. 20 0
      vendor/meilisearch/meilisearch-php/tests/Http/ClientTest.php
  74. 227 0
      vendor/meilisearch/meilisearch-php/tests/Search/SearchResultTest.php
  75. 55 0
      vendor/meilisearch/meilisearch-php/tests/Settings/AttributesForFacetingTest.php
  76. 60 0
      vendor/meilisearch/meilisearch-php/tests/Settings/DisplayedAttributesTest.php
  77. 48 0
      vendor/meilisearch/meilisearch-php/tests/Settings/DistinctAttributeTest.php
  78. 67 0
      vendor/meilisearch/meilisearch-php/tests/Settings/RankingRulesTest.php
  79. 57 0
      vendor/meilisearch/meilisearch-php/tests/Settings/SearchableAttributesTest.php
  80. 140 0
      vendor/meilisearch/meilisearch-php/tests/Settings/SettingsTest.php
  81. 56 0
      vendor/meilisearch/meilisearch-php/tests/Settings/StopWordsTest.php
  82. 59 0
      vendor/meilisearch/meilisearch-php/tests/Settings/SynonymsTest.php
  83. 47 0
      vendor/meilisearch/meilisearch-php/tests/TestCase.php
  84. 24 0
      vendor/php-http/client-common/.php_cs.dist
  85. 276 0
      vendor/php-http/client-common/CHANGELOG.md
  86. 19 0
      vendor/php-http/client-common/LICENSE
  87. 55 0
      vendor/php-http/client-common/README.md
  88. 67 0
      vendor/php-http/client-common/composer.json
  89. 42 0
      vendor/php-http/client-common/src/BatchClient.php
  90. 34 0
      vendor/php-http/client-common/src/BatchClientInterface.php
  91. 157 0
      vendor/php-http/client-common/src/BatchResult.php
  92. 152 0
      vendor/php-http/client-common/src/Deferred.php
  93. 25 0
      vendor/php-http/client-common/src/EmulatedHttpAsyncClient.php
  94. 24 0
      vendor/php-http/client-common/src/EmulatedHttpClient.php
  95. 37 0
      vendor/php-http/client-common/src/Exception/BatchException.php
  96. 16 0
      vendor/php-http/client-common/src/Exception/CircularRedirectionException.php
  97. 16 0
      vendor/php-http/client-common/src/Exception/ClientErrorException.php
  98. 33 0
      vendor/php-http/client-common/src/Exception/HttpClientNoMatchException.php
  99. 16 0
      vendor/php-http/client-common/src/Exception/HttpClientNotFoundException.php
  100. 16 0
      vendor/php-http/client-common/src/Exception/LoopException.php

+ 4 - 1
composer.json

@@ -44,7 +44,10 @@
         "pda/pheanstalk": "^4.0",
         "robthree/twofactorauth": "^1.8",
         "yurunsoft/yurun-oauth-login": "^3.0",
-        "yurunsoft/pay-sdk": "^3.1"
+        "yurunsoft/pay-sdk": "^3.1",
+        "meilisearch/meilisearch-php": "^0.17.2",
+        "php-http/guzzle6-adapter": "^2.0",
+        "http-interop/http-factory-guzzle": "^1.0"
     },
     "autoload": {
         "psr-4": {

Diferenças do arquivo suprimidas por serem muito extensas
+ 784 - 85
composer.lock


+ 6 - 0
config/meilisearch.php

@@ -0,0 +1,6 @@
+<?php
+
+return [
+    'addr' => '127.0.0.1:7700',
+    'key' => '',
+];

+ 2 - 1
tw/command/Maintain.php

@@ -13,6 +13,7 @@ use think\console\Input;
 use think\console\input\Argument;
 use think\console\Output;
 use \think\facade\Config;
+use MeiliSearch\Client;
 
 /**
  * 维护命令,使用 nginx 帐号运行
@@ -326,6 +327,6 @@ class Maintain extends Command
 
     protected function none()
     {
-        
+
     }
 }

+ 2 - 0
vendor/clue/stream-filter/.github/FUNDING.yml

@@ -0,0 +1,2 @@
+github: clue
+custom: https://clue.engineering/support

+ 86 - 0
vendor/clue/stream-filter/CHANGELOG.md

@@ -0,0 +1,86 @@
+# Changelog
+
+## 1.6.0 (2022-02-21)
+
+*   Feature: Support PHP 8.1 release.
+    (#45 by @clue)
+
+*   Improve documentation to use fully-qualified function names.
+    (#43 by @SimonFrings and #42 by @PaulRotmann)
+
+*   Improve test suite and use GitHub actions for continuous integration (CI).
+    (#39 and #40 by @SimonFrings)
+
+## 1.5.0 (2020-10-02)
+
+*   Feature: Improve performance by using global imports.
+    (#38 by @clue)
+
+*   Improve API documentation and add support / sponsorship info.
+    (#30 by @clue and #35 by @SimonFrings)
+
+*   Improve test suite and add `.gitattributes` to exclude dev files from exports.
+    Prepare PHP 8 support, update to PHPUnit 9 and simplify test matrix.
+    (#32 and #37 by @clue and #34 and #36 by @SimonFrings)
+
+## 1.4.1 (2019-04-09)
+
+*   Fix: Check if the function is declared before declaring it.
+    (#23 by @Niko9911)
+
+*   Improve test suite to also test against PHP 7.2 and
+    add test for base64 encoding and decoding filters.
+    (#22 by @arubacao and #25 by @Nyholm and @clue)
+
+## 1.4.0 (2017-08-18)
+
+*   Feature / Fix: The `fun()` function does not pass filter parameter `null`
+    to underlying `stream_filter_append()` by default
+    (#15 by @Nyholm)
+
+    Certain filters (such as `convert.quoted-printable-encode`) do not accept
+    a filter parameter at all. If no explicit filter parameter is given, we no
+    longer pass a default `null` value.
+
+    ```php
+    $encode = Filter\fun('convert.quoted-printable-encode');
+    assert('t=C3=A4st' === $encode('täst'));
+    ```
+
+*   Add examples and improve documentation
+    (#13 and #20 by @clue and #18 by @Nyholm)
+
+*   Improve test suite by adding PHPUnit to require-dev,
+    fix HHVM build for now again and ignore future HHVM build errors,
+    lock Travis distro so new future defaults will not break the build
+    and test on PHP 7.1
+    (#12, #14 and #19 by @clue and #16 by @Nyholm)
+
+## 1.3.0 (2015-11-08)
+
+*   Feature: Support accessing built-in filters as callbacks
+    (#5 by @clue)
+
+    ```php
+    $fun = Filter\fun('zlib.deflate');
+
+    $ret = $fun('hello') . $fun('world') . $fun();
+    assert('helloworld' === gzinflate($ret));
+    ```
+
+## 1.2.0 (2015-10-23)
+
+* Feature: Invoke close event when closing filter (flush buffer)
+  (#9 by @clue)
+
+## 1.1.0 (2015-10-22)
+
+* Feature: Abort filter operation when catching an Exception
+  (#10 by @clue)
+
+* Feature: Additional safeguards to prevent filter state corruption
+  (#7 by @clue)
+
+## 1.0.0 (2015-10-18)
+
+* First tagged release

+ 21 - 0
vendor/clue/stream-filter/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Christian Lück
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 326 - 0
vendor/clue/stream-filter/README.md

@@ -0,0 +1,326 @@
+# clue/stream-filter
+
+[![CI status](https://github.com/clue/stream-filter/workflows/CI/badge.svg)](https://github.com/clue/stream-filter/actions)
+[![installs on Packagist](https://img.shields.io/packagist/dt/clue/stream-filter?color=blue&label=installs%20on%20Packagist)](https://packagist.org/packages/clue/stream-filter)
+
+A simple and modern approach to stream filtering in PHP
+
+**Table of contents**
+
+* [Why?](#why)
+* [Support us](#support-us)
+* [Usage](#usage)
+    * [append()](#append)
+    * [prepend()](#prepend)
+    * [fun()](#fun)
+    * [remove()](#remove)
+* [Install](#install)
+* [Tests](#tests)
+* [License](#license)
+
+## Why?
+
+PHP's stream filtering system is great!
+
+It offers very powerful stream filtering options and comes with a useful set of built-in filters.
+These filters can be used to easily and efficiently perform various transformations on-the-fly, such as:
+
+* read from a gzip'ed input file,
+* transcode from ISO-8859-1 (Latin1) to UTF-8,
+* write to a bzip output file
+* and much more.
+
+But let's face it:
+Its API is [*difficult to work with*](https://www.php.net/manual/en/php-user-filter.filter.php)
+and its documentation is [*subpar*](https://stackoverflow.com/questions/27103269/what-is-a-bucket-brigade).
+This combined means its powerful features are often neglected.
+
+This project aims to make these features more accessible to a broader audience.
+* **Lightweight, SOLID design** -
+  Provides a thin abstraction that is [*just good enough*](https://en.wikipedia.org/wiki/Principle_of_good_enough)
+  and does not get in your way.
+  Custom filters require trivial effort.
+* **Good test coverage** -
+  Comes with an automated tests suite and is regularly tested in the *real world*
+
+## Support us
+
+We invest a lot of time developing, maintaining and updating our awesome
+open-source projects. You can help us sustain this high-quality of our work by
+[becoming a sponsor on GitHub](https://github.com/sponsors/clue). Sponsors get
+numerous benefits in return, see our [sponsoring page](https://github.com/sponsors/clue)
+for details.
+
+Let's take these projects to the next level together! 🚀
+
+## Usage
+
+This lightweight library consists only of a few simple functions.
+All functions reside under the `Clue\StreamFilter` namespace.
+
+The below examples refer to all functions with their fully-qualified names like this:
+
+```php
+Clue\StreamFilter\append(…);
+```
+
+As of PHP 5.6+ you can also import each required function into your code like this:
+
+```php
+use function Clue\StreamFilter\append;
+
+append(…);
+```
+
+Alternatively, you can also use an import statement similar to this:
+
+```php
+use Clue\StreamFilter as Filter;
+
+Filter\append(…);
+```
+
+### append()
+
+The `append(resource<stream> $stream, callable $callback, int $read_write = STREAM_FILTER_ALL): resource<stream filter>` function can be used to
+append a filter callback to the given stream.
+
+Each stream can have a list of filters attached.
+This function appends a filter to the end of this list.
+
+If the given filter can not be added, it throws an `Exception`.
+
+The `$stream` can be any valid stream resource, such as:
+
+```php
+$stream = fopen('demo.txt', 'w+');
+```
+
+The `$callback` should be a valid callable function which accepts 
+an individual chunk of data and should return the updated chunk:
+
+```php
+$filter = Clue\StreamFilter\append($stream, function ($chunk) {
+    // will be called each time you read or write a $chunk to/from the stream
+    return $chunk;
+});
+```
+
+As such, you can also use native PHP functions or any other `callable`:
+
+```php
+Clue\StreamFilter\append($stream, 'strtoupper');
+
+// will write "HELLO" to the underlying stream
+fwrite($stream, 'hello');
+```
+
+If the `$callback` accepts invocation without parameters,
+then this signature will be invoked once ending (flushing) the filter:
+
+```php
+Clue\StreamFilter\append($stream, function ($chunk = null) {
+    if ($chunk === null) {
+        // will be called once ending the filter
+        return 'end';
+    }
+    // will be called each time you read or write a $chunk to/from the stream
+    return $chunk;
+});
+
+fclose($stream);
+```
+
+> Note: Legacy PHP versions (PHP < 5.4) do not support passing additional data
+from the end signal handler if the stream is being closed.
+
+If your callback throws an `Exception`, then the filter process will be aborted.
+In order to play nice with PHP's stream handling,
+the `Exception` will be transformed to a PHP warning instead:
+
+```php
+Clue\StreamFilter\append($stream, function ($chunk) {
+    throw new \RuntimeException('Unexpected chunk');
+});
+
+// raises an E_USER_WARNING with "Error invoking filter: Unexpected chunk"
+fwrite($stream, 'hello');
+```
+
+The optional `$read_write` parameter can be used to only invoke the `$callback`
+when either writing to the stream or only when reading from the stream:
+
+```php
+Clue\StreamFilter\append($stream, function ($chunk) {
+    // will be called each time you write to the stream
+    return $chunk;
+}, STREAM_FILTER_WRITE);
+
+Clue\StreamFilter\append($stream, function ($chunk) {
+    // will be called each time you read from the stream
+    return $chunk;
+}, STREAM_FILTER_READ);
+```
+
+This function returns a filter resource which can be passed to [`remove()`](#remove).
+
+> Note that once a filter has been added to stream, the stream can no longer be passed to
+> [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php)
+> (and family).
+>
+> > Warning: stream_select(): cannot cast a filtered stream on this system in {file} on line {line}
+>
+> This is due to limitations of PHP's stream filter support, as it can no longer reliably
+> tell when the underlying stream resource is actually ready.
+> As an alternative, consider calling `stream_select()` on the unfiltered stream and
+> then pass the unfiltered data through the [`fun()`](#fun) function.
+
+### prepend()
+
+The `prepend(resource<stream> $stream, callable $callback, int $read_write = STREAM_FILTER_ALL): resource<stream filter>` function can be used to
+prepend a filter callback to the given stream.
+
+Each stream can have a list of filters attached.
+This function prepends a filter to the start of this list.
+
+If the given filter can not be added, it throws an `Exception`.
+
+```php
+$filter = Clue\StreamFilter\prepend($stream, function ($chunk) {
+    // will be called each time you read or write a $chunk to/from the stream
+    return $chunk;
+});
+```
+
+This function returns a filter resource which can be passed to [`remove()`](#remove).
+
+Except for the position in the list of filters, this function behaves exactly
+like the [`append()`](#append) function.
+For more details about its behavior, see also the [`append()`](#append) function.
+
+### fun()
+
+The `fun(string $filter, mixed $parameters = null): callable` function can be used to
+create a filter function which uses the given built-in `$filter`.
+
+PHP comes with a useful set of [built-in filters](https://www.php.net/manual/en/filters.php).
+Using `fun()` makes accessing these as easy as passing an input string to filter
+and getting the filtered output string.
+
+```php
+$fun = Clue\StreamFilter\fun('string.rot13');
+
+assert('grfg' === $fun('test'));
+assert('test' === $fun($fun('test'));
+```
+
+Please note that not all filter functions may be available depending 
+on installed PHP extensions and the PHP version in use.
+In particular, [HHVM](https://hhvm.com/) may not offer the same filter functions
+or parameters as Zend PHP.
+Accessing an unknown filter function will result in a `RuntimeException`:
+
+```php
+Clue\StreamFilter\fun('unknown'); // throws RuntimeException
+```
+
+Some filters may accept or require additional filter parameters – most
+filters do not require filter parameters.
+If given, the optional `$parameters` argument will be passed to the
+underlying filter handler as-is.
+In particular, note how *not passing* this parameter at all differs from
+explicitly passing a `null` value (which many filters do not accept).
+Please refer to the individual filter definition for more details.
+For example, the `string.strip_tags` filter can be invoked like this:
+
+```php
+$fun = Clue\StreamFilter\fun('string.strip_tags', '<a><b>');
+
+$ret = $fun('<b>h<br>i</b>');
+assert('<b>hi</b>' === $ret);
+```
+
+Under the hood, this function allocates a temporary memory stream, so it's
+recommended to clean up the filter function after use.
+Also, some filter functions (in particular the
+[zlib compression filters](https://www.php.net/manual/en/filters.compression.php))
+may use internal buffers and may emit a final data chunk on close.
+The filter function can be closed by invoking without any arguments:
+
+```php
+$fun = Clue\StreamFilter\fun('zlib.deflate');
+
+$ret = $fun('hello') . $fun('world') . $fun();
+assert('helloworld' === gzinflate($ret));
+```
+
+The filter function must not be used anymore after it has been closed.
+Doing so will result in a `RuntimeException`:
+
+```php
+$fun = Clue\StreamFilter\fun('string.rot13');
+$fun();
+
+$fun('test'); // throws RuntimeException
+```
+
+> Note: If you're using the zlib compression filters, then you should be wary
+about engine inconsistencies between different PHP versions and HHVM.
+These inconsistencies exist in the underlying PHP engines and there's little we
+can do about this in this library.
+[Our test suite](tests/) contains several test cases that exhibit these issues.
+If you feel some test case is missing or outdated, we're happy to accept PRs! :)
+
+### remove()
+
+The `remove(resource<stream filter> $filter): bool` function can be used to
+remove a filter previously added via [`append()`](#append) or [`prepend()`](#prepend).
+
+```php
+$filter = Clue\StreamFilter\append($stream, function () {
+    // …
+});
+Clue\StreamFilter\remove($filter);
+```
+
+## Install
+
+The recommended way to install this library is [through Composer](https://getcomposer.org/).
+[New to Composer?](https://getcomposer.org/doc/00-intro.md)
+
+This project follows [SemVer](https://semver.org/).
+This will install the latest supported version:
+
+```bash
+$ composer require clue/stream-filter:^1.6
+```
+
+See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades.
+
+This project aims to run on any platform and thus does not require any PHP
+extensions and supports running on legacy PHP 5.3 through current PHP 8+ and
+HHVM.
+It's *highly recommended to use the latest supported PHP version* for this project.
+Older PHP versions may suffer from a number of inconsistencies documented above.
+
+## Tests
+
+To run the test suite, you first need to clone this repo and then install all
+dependencies [through Composer](https://getcomposer.org/):
+
+```bash
+$ composer install
+```
+
+To run the test suite, go to the project root and run:
+
+```bash
+$ vendor/bin/phpunit
+```
+
+## License
+
+This project is released under the permissive [MIT license](LICENSE).
+
+> Did you know that I offer custom development services and issuing invoices for
+  sponsorships of releases and for contributions? Contact me (@clue) for details.

+ 26 - 0
vendor/clue/stream-filter/composer.json

@@ -0,0 +1,26 @@
+{
+    "name": "clue/stream-filter",
+    "description": "A simple and modern approach to stream filtering in PHP",
+    "keywords": ["stream", "callback", "filter", "php_user_filter", "stream_filter_append", "stream_filter_register", "bucket brigade"],
+    "homepage": "https://github.com/clue/php-stream-filter",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Christian Lück",
+            "email": "christian@clue.engineering"
+        }
+    ],
+    "require": {
+        "php": ">=5.3"
+    },
+    "require-dev": {
+        "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.36"
+    },
+    "autoload": {
+        "psr-4": { "Clue\\StreamFilter\\": "src/" },
+        "files": [ "src/functions_include.php" ]
+    },
+    "autoload-dev": {
+        "psr-4": { "Clue\\Tests\\StreamFilter\\": "tests/" }
+    }
+}

+ 120 - 0
vendor/clue/stream-filter/src/CallbackFilter.php

@@ -0,0 +1,120 @@
+<?php
+
+namespace Clue\StreamFilter;
+
+/**
+ * @internal
+ * @see append()
+ * @see prepend()
+ */
+class CallbackFilter extends \php_user_filter
+{
+    private $callback;
+    private $closed = true;
+    private $supportsClose = false;
+
+    /** @return bool */
+    #[\ReturnTypeWillChange]
+    public function onCreate()
+    {
+        $this->closed = false;
+
+        if (!\is_callable($this->params)) {
+            throw new \InvalidArgumentException('No valid callback parameter given to stream_filter_(append|prepend)');
+        }
+        $this->callback = $this->params;
+
+        // callback supports end event if it accepts invocation without arguments
+        $ref = new \ReflectionFunction($this->callback);
+        $this->supportsClose = ($ref->getNumberOfRequiredParameters() === 0);
+
+        return true;
+    }
+
+    /** @return void */
+    #[\ReturnTypeWillChange]
+    public function onClose()
+    {
+        $this->closed = true;
+
+        // callback supports closing and is not already closed
+        if ($this->supportsClose) {
+            $this->supportsClose = false;
+            // invoke without argument to signal end and discard resulting buffer
+            try {
+                \call_user_func($this->callback);
+            } catch (\Exception $ignored) {
+                // this might be called during engine shutdown, so it's not safe
+                // to raise any errors or exceptions here
+                // trigger_error('Error closing filter: ' . $ignored->getMessage(), E_USER_WARNING);
+            }
+        }
+
+        $this->callback = null;
+    }
+
+    /** @return int */
+    #[\ReturnTypeWillChange]
+    public function filter($in, $out, &$consumed, $closing)
+    {
+        // concatenate whole buffer from input brigade
+        $data = '';
+        while ($bucket = \stream_bucket_make_writeable($in)) {
+            $consumed += $bucket->datalen;
+            $data .= $bucket->data;
+        }
+
+        // skip processing callback that already ended
+        if ($this->closed) {
+            return \PSFS_FEED_ME;
+        }
+
+        // only invoke filter function if buffer is not empty
+        // this may skip flushing a closing filter
+        if ($data !== '') {
+            try {
+                $data = \call_user_func($this->callback, $data);
+            } catch (\Exception $e) {
+                // exception should mark filter as closed
+                $this->onClose();
+                \trigger_error('Error invoking filter: ' . $e->getMessage(), \E_USER_WARNING);
+
+                return \PSFS_ERR_FATAL;
+            }
+        }
+
+        // mark filter as closed after processing closing chunk
+        if ($closing) {
+            $this->closed = true;
+
+            // callback supports closing and is not already closed
+            if ($this->supportsClose) {
+                $this->supportsClose = false;
+
+                // invoke without argument to signal end and append resulting buffer
+                try {
+                    $data .= \call_user_func($this->callback);
+                } catch (\Exception $e) {
+                    \trigger_error('Error ending filter: ' . $e->getMessage(), \E_USER_WARNING);
+
+                    return \PSFS_ERR_FATAL;
+                }
+            }
+        }
+
+        if ($data !== '') {
+            // create a new bucket for writing the resulting buffer to the output brigade
+            // reusing an existing bucket turned out to be bugged in some environments (ancient PHP versions and HHVM)
+            $bucket = @\stream_bucket_new($this->stream, $data);
+
+            // legacy PHP versions (PHP < 5.4) do not support passing data from the event signal handler
+            // because closing the stream invalidates the stream and its stream bucket brigade before
+            // invoking the filter close handler.
+            if ($bucket !== false) {
+                \stream_bucket_append($out, $bucket);
+            }
+        }
+
+        return \PSFS_PASS_ON;
+    }
+}

+ 327 - 0
vendor/clue/stream-filter/src/functions.php

@@ -0,0 +1,327 @@
+<?php
+
+namespace Clue\StreamFilter;
+
+/**
+ * Append a filter callback to the given stream.
+ *
+ * Each stream can have a list of filters attached.
+ * This function appends a filter to the end of this list.
+ *
+ * If the given filter can not be added, it throws an `Exception`.
+ *
+ * The `$stream` can be any valid stream resource, such as:
+ *
+ * ```php
+ * $stream = fopen('demo.txt', 'w+');
+ * ```
+ *
+ * The `$callback` should be a valid callable function which accepts
+ * an individual chunk of data and should return the updated chunk:
+ *
+ * ```php
+ * $filter = Clue\StreamFilter\append($stream, function ($chunk) {
+ *     // will be called each time you read or write a $chunk to/from the stream
+ *     return $chunk;
+ * });
+ * ```
+ *
+ * As such, you can also use native PHP functions or any other `callable`:
+ *
+ * ```php
+ * Clue\StreamFilter\append($stream, 'strtoupper');
+ *
+ * // will write "HELLO" to the underlying stream
+ * fwrite($stream, 'hello');
+ * ```
+ *
+ * If the `$callback` accepts invocation without parameters,
+ * then this signature will be invoked once ending (flushing) the filter:
+ *
+ * ```php
+ * Clue\StreamFilter\append($stream, function ($chunk = null) {
+ *     if ($chunk === null) {
+ *         // will be called once ending the filter
+ *         return 'end';
+ *     }
+ *     // will be called each time you read or write a $chunk to/from the stream
+ *     return $chunk;
+ * });
+ *
+ * fclose($stream);
+ * ```
+ *
+ * > Note: Legacy PHP versions (PHP < 5.4) do not support passing additional data
+ * from the end signal handler if the stream is being closed.
+ *
+ * If your callback throws an `Exception`, then the filter process will be aborted.
+ * In order to play nice with PHP's stream handling,
+ * the `Exception` will be transformed to a PHP warning instead:
+ *
+ * ```php
+ * Clue\StreamFilter\append($stream, function ($chunk) {
+ *     throw new \RuntimeException('Unexpected chunk');
+ * });
+ *
+ * // raises an E_USER_WARNING with "Error invoking filter: Unexpected chunk"
+ * fwrite($stream, 'hello');
+ * ```
+ *
+ * The optional `$read_write` parameter can be used to only invoke the `$callback`
+ * when either writing to the stream or only when reading from the stream:
+ *
+ * ```php
+ * Clue\StreamFilter\append($stream, function ($chunk) {
+ *     // will be called each time you write to the stream
+ *     return $chunk;
+ * }, STREAM_FILTER_WRITE);
+ *
+ * Clue\StreamFilter\append($stream, function ($chunk) {
+ *     // will be called each time you read from the stream
+ *     return $chunk;
+ * }, STREAM_FILTER_READ);
+ * ```
+ *
+ * This function returns a filter resource which can be passed to [`remove()`](#remove).
+ *
+ * > Note that once a filter has been added to stream, the stream can no longer be passed to
+ * > [`stream_select()`](https://www.php.net/manual/en/function.stream-select.php)
+ * > (and family).
+ * >
+ * > > Warning: stream_select(): cannot cast a filtered stream on this system in {file} on line {line}
+ * >
+ * > This is due to limitations of PHP's stream filter support, as it can no longer reliably
+ * > tell when the underlying stream resource is actually ready.
+ * > As an alternative, consider calling `stream_select()` on the unfiltered stream and
+ * > then pass the unfiltered data through the [`fun()`](#fun) function.
+ *
+ * @param resource $stream
+ * @param callable $callback
+ * @param int $read_write
+ * @return resource filter resource which can be used for `remove()`
+ * @throws \Exception on error
+ * @uses stream_filter_append()
+ */
+function append($stream, $callback, $read_write = STREAM_FILTER_ALL)
+{
+    $ret = @\stream_filter_append($stream, register(), $read_write, $callback);
+
+    // PHP 8 throws above on type errors, older PHP and memory issues can throw here
+    // @codeCoverageIgnoreStart
+    if ($ret === false) {
+        $error = \error_get_last() + array('message' => '');
+        throw new \RuntimeException('Unable to append filter: ' . $error['message']);
+    }
+    // @codeCoverageIgnoreEnd
+
+    return $ret;
+}
+
+/**
+ * Prepend a filter callback to the given stream.
+ *
+ * Each stream can have a list of filters attached.
+ * This function prepends a filter to the start of this list.
+ *
+ * If the given filter can not be added, it throws an `Exception`.
+ *
+ * ```php
+ * $filter = Clue\StreamFilter\prepend($stream, function ($chunk) {
+ *     // will be called each time you read or write a $chunk to/from the stream
+ *     return $chunk;
+ * });
+ * ```
+ *
+ * This function returns a filter resource which can be passed to [`remove()`](#remove).
+ *
+ * Except for the position in the list of filters, this function behaves exactly
+ * like the [`append()`](#append) function.
+ * For more details about its behavior, see also the [`append()`](#append) function.
+ *
+ * @param resource $stream
+ * @param callable $callback
+ * @param int $read_write
+ * @return resource filter resource which can be used for `remove()`
+ * @throws \Exception on error
+ * @uses stream_filter_prepend()
+ */
+function prepend($stream, $callback, $read_write = STREAM_FILTER_ALL)
+{
+    $ret = @\stream_filter_prepend($stream, register(), $read_write, $callback);
+
+    // PHP 8 throws above on type errors, older PHP and memory issues can throw here
+    // @codeCoverageIgnoreStart
+    if ($ret === false) {
+        $error = \error_get_last() + array('message' => '');
+        throw new \RuntimeException('Unable to prepend filter: ' . $error['message']);
+    }
+    // @codeCoverageIgnoreEnd
+
+    return $ret;
+}
+
+/**
+ * Create a filter function which uses the given built-in `$filter`.
+ *
+ * PHP comes with a useful set of [built-in filters](https://www.php.net/manual/en/filters.php).
+ * Using `fun()` makes accessing these as easy as passing an input string to filter
+ * and getting the filtered output string.
+ *
+ * ```php
+ * $fun = Clue\StreamFilter\fun('string.rot13');
+ *
+ * assert('grfg' === $fun('test'));
+ * assert('test' === $fun($fun('test'));
+ * ```
+ *
+ * Please note that not all filter functions may be available depending
+ * on installed PHP extensions and the PHP version in use.
+ * In particular, [HHVM](https://hhvm.com/) may not offer the same filter functions
+ * or parameters as Zend PHP.
+ * Accessing an unknown filter function will result in a `RuntimeException`:
+ *
+ * ```php
+ * Clue\StreamFilter\fun('unknown'); // throws RuntimeException
+ * ```
+ *
+ * Some filters may accept or require additional filter parameters – most
+ * filters do not require filter parameters.
+ * If given, the optional `$parameters` argument will be passed to the
+ * underlying filter handler as-is.
+ * In particular, note how *not passing* this parameter at all differs from
+ * explicitly passing a `null` value (which many filters do not accept).
+ * Please refer to the individual filter definition for more details.
+ * For example, the `string.strip_tags` filter can be invoked like this:
+ *
+ * ```php
+ * $fun = Clue\StreamFilter\fun('string.strip_tags', '<a><b>');
+ *
+ * $ret = $fun('<b>h<br>i</b>');
+ * assert('<b>hi</b>' === $ret);
+ * ```
+ *
+ * Under the hood, this function allocates a temporary memory stream, so it's
+ * recommended to clean up the filter function after use.
+ * Also, some filter functions (in particular the
+ * [zlib compression filters](https://www.php.net/manual/en/filters.compression.php))
+ * may use internal buffers and may emit a final data chunk on close.
+ * The filter function can be closed by invoking without any arguments:
+ *
+ * ```php
+ * $fun = Clue\StreamFilter\fun('zlib.deflate');
+ *
+ * $ret = $fun('hello') . $fun('world') . $fun();
+ * assert('helloworld' === gzinflate($ret));
+ * ```
+ *
+ * The filter function must not be used anymore after it has been closed.
+ * Doing so will result in a `RuntimeException`:
+ *
+ * ```php
+ * $fun = Clue\StreamFilter\fun('string.rot13');
+ * $fun();
+ *
+ * $fun('test'); // throws RuntimeException
+ * ```
+ *
+ * > Note: If you're using the zlib compression filters, then you should be wary
+ * about engine inconsistencies between different PHP versions and HHVM.
+ * These inconsistencies exist in the underlying PHP engines and there's little we
+ * can do about this in this library.
+ * [Our test suite](tests/) contains several test cases that exhibit these issues.
+ * If you feel some test case is missing or outdated, we're happy to accept PRs! :)
+ *
+ * @param string $filter     built-in filter name. See stream_get_filters() or http://php.net/manual/en/filters.php
+ * @param mixed  $parameters (optional) parameters to pass to the built-in filter as-is
+ * @return callable a filter callback which can be append()'ed or prepend()'ed
+ * @throws \RuntimeException on error
+ * @link http://php.net/manual/en/filters.php
+ * @see stream_get_filters()
+ * @see append()
+ */
+function fun($filter, $parameters = null)
+{
+    $fp = \fopen('php://memory', 'w');
+    if (\func_num_args() === 1) {
+        $filter = @\stream_filter_append($fp, $filter, \STREAM_FILTER_WRITE);
+    } else {
+        $filter = @\stream_filter_append($fp, $filter, \STREAM_FILTER_WRITE, $parameters);
+    }
+
+    if ($filter === false) {
+        \fclose($fp);
+        $error = \error_get_last() + array('message' => '');
+        throw new \RuntimeException('Unable to access built-in filter: ' . $error['message']);
+    }
+
+    // append filter function which buffers internally
+    $buffer = '';
+    append($fp, function ($chunk) use (&$buffer) {
+        $buffer .= $chunk;
+
+        // always return empty string in order to skip actually writing to stream resource
+        return '';
+    }, \STREAM_FILTER_WRITE);
+
+    $closed = false;
+
+    return function ($chunk = null) use ($fp, $filter, &$buffer, &$closed) {
+        if ($closed) {
+            throw new \RuntimeException('Unable to perform operation on closed stream');
+        }
+        if ($chunk === null) {
+            $closed = true;
+            $buffer = '';
+            \fclose($fp);
+            return $buffer;
+        }
+        // initialize buffer and invoke filters by attempting to write to stream
+        $buffer = '';
+        \fwrite($fp, $chunk);
+
+        // buffer now contains everything the filter function returned
+        return $buffer;
+    };
+}
+
+/**
+ * Remove a filter previously added via `append()` or `prepend()`.
+ *
+ * ```php
+ * $filter = Clue\StreamFilter\append($stream, function () {
+ *     // …
+ * });
+ * Clue\StreamFilter\remove($filter);
+ * ```
+ *
+ * @param resource $filter
+ * @return bool true on success or false on error
+ * @throws \RuntimeException on error
+ * @uses stream_filter_remove()
+ */
+function remove($filter)
+{
+    if (@\stream_filter_remove($filter) === false) {
+        // PHP 8 throws above on type errors, older PHP and memory issues can throw here
+        $error = \error_get_last();
+        throw new \RuntimeException('Unable to remove filter: ' . $error['message']);
+    }
+}
+
+/**
+ * Registers the callback filter and returns the resulting filter name
+ *
+ * There should be little reason to call this function manually.
+ *
+ * @return string filter name
+ * @uses CallbackFilter
+ */
+function register()
+{
+    static $registered = null;
+    if ($registered === null) {
+        $registered = 'stream-callback';
+        \stream_filter_register($registered, __NAMESPACE__ . '\CallbackFilter');
+    }
+    return $registered;
+}

+ 6 - 0
vendor/clue/stream-filter/src/functions_include.php

@@ -0,0 +1,6 @@
+<?php
+
+// @codeCoverageIgnoreStart
+if (!\function_exists('Clue\\StreamFilter\\append')) {
+    require __DIR__ . '/functions.php';
+}

+ 6 - 0
vendor/composer/autoload_classmap.php

@@ -8,9 +8,11 @@ $baseDir = dirname($vendorDir);
 return array(
     'ArithmeticError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php',
     'AssertionError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php',
+    'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
     'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
     'DivisionByZeroError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php',
     'Error' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/Error.php',
+    'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
     'PHPUnit\\Exception' => $vendorDir . '/phpunit/phpunit/src/Exception.php',
     'PHPUnit\\Framework\\Assert' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert.php',
     'PHPUnit\\Framework\\AssertionFailedError' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception/AssertionFailedError.php',
@@ -472,6 +474,7 @@ return array(
     'PharIo\\Version\\VersionConstraintParser' => $vendorDir . '/phar-io/version/src/VersionConstraintParser.php',
     'PharIo\\Version\\VersionConstraintValue' => $vendorDir . '/phar-io/version/src/VersionConstraintValue.php',
     'PharIo\\Version\\VersionNumber' => $vendorDir . '/phar-io/version/src/VersionNumber.php',
+    'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
     'SebastianBergmann\\CodeCoverage\\CodeCoverage' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage.php',
     'SebastianBergmann\\CodeCoverage\\CoveredCodeNotExecutedException' => $vendorDir . '/phpunit/php-code-coverage/src/Exception/CoveredCodeNotExecutedException.php',
     'SebastianBergmann\\CodeCoverage\\Driver\\Driver' => $vendorDir . '/phpunit/php-code-coverage/src/Driver/Driver.php',
@@ -585,6 +588,7 @@ return array(
     'SebastianBergmann\\Type\\VoidType' => $vendorDir . '/sebastian/type/src/VoidType.php',
     'SebastianBergmann\\Version' => $vendorDir . '/sebastian/version/src/Version.php',
     'SessionUpdateTimestampHandlerInterface' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php',
+    'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
     'Text_Template' => $vendorDir . '/phpunit/php-text-template/src/Template.php',
     'TheSeer\\Tokenizer\\Exception' => $vendorDir . '/theseer/tokenizer/src/Exception.php',
     'TheSeer\\Tokenizer\\NamespaceUri' => $vendorDir . '/theseer/tokenizer/src/NamespaceUri.php',
@@ -595,4 +599,6 @@ return array(
     'TheSeer\\Tokenizer\\Tokenizer' => $vendorDir . '/theseer/tokenizer/src/Tokenizer.php',
     'TheSeer\\Tokenizer\\XMLSerializer' => $vendorDir . '/theseer/tokenizer/src/XMLSerializer.php',
     'TypeError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/TypeError.php',
+    'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
+    'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
 );

+ 7 - 2
vendor/composer/autoload_files.php

@@ -7,10 +7,10 @@ $baseDir = dirname($vendorDir);
 
 return array(
     '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
-    '9b552a3cc426e3287cc811caefa3cf53' => $vendorDir . '/topthink/think-helper/src/helper.php',
     '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
-    'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
     'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
+    '9b552a3cc426e3287cc811caefa3cf53' => $vendorDir . '/topthink/think-helper/src/helper.php',
+    'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
     '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
     '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
     '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
@@ -18,8 +18,13 @@ return array(
     '538ca81a9a966a6716601ecf48f4eaef' => $vendorDir . '/opis/closure/functions.php',
     '023d27dca8066ef29e6739335ea73bad' => $vendorDir . '/symfony/polyfill-php70/bootstrap.php',
     '65fec9ebcfbb3cbb4fd0d519687aea01' => $vendorDir . '/danielstjules/stringy/src/Create.php',
+    'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
+    '9c67151ae59aff4788964ce8eb2a0f43' => $vendorDir . '/clue/stream-filter/src/functions_include.php',
     'b067bc7112e384b61c701452d53a14a8' => $vendorDir . '/mtdowling/jmespath.php/src/JmesPath.php',
+    '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
     '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
+    '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
+    '8cff32064859f4559445b89279f3199c' => $vendorDir . '/php-http/message/src/filters.php',
     '66453932bc1be9fb2f910a27947d11b6' => $vendorDir . '/alibabacloud/client/src/Functions.php',
     '6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
     '0d0b82117c23db94c492fee02b2ed01f' => $vendorDir . '/songshenzong/support/src/StringsHelpers.php',

+ 14 - 1
vendor/composer/autoload_psr4.php

@@ -24,6 +24,8 @@ return array(
     'Yurun\\OAuthLogin\\' => array($vendorDir . '/yurunsoft/yurun-oauth-login/src'),
     'Workerman\\' => array($vendorDir . '/workerman/workerman'),
     'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
+    'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
+    'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'),
     'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'),
     'Symfony\\Polyfill\\Php70\\' => array($vendorDir . '/symfony/polyfill-php70'),
     'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
@@ -32,6 +34,7 @@ return array(
     'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
     'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'),
     'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
+    'Symfony\\Component\\OptionsResolver\\' => array($vendorDir . '/symfony/options-resolver'),
     'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'),
     'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
     'Symfony\\Bridge\\PsrHttpMessage\\' => array($vendorDir . '/symfony/psr-http-message-bridge'),
@@ -42,7 +45,8 @@ return array(
     'Qiniu\\' => array($vendorDir . '/qiniu/php-sdk/src/Qiniu'),
     'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
     'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
-    'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
+    'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
+    'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
     'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
     'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
     'Prophecy\\' => array($vendorDir . '/phpspec/prophecy/src/Prophecy'),
@@ -51,9 +55,17 @@ return array(
     'Opis\\Closure\\' => array($vendorDir . '/opis/closure/src'),
     'OSS\\' => array($vendorDir . '/aliyuncs/oss-sdk-php/src/OSS'),
     'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
+    'MeiliSearch\\' => array($vendorDir . '/meilisearch/meilisearch-php/src'),
     'League\\Flysystem\\Cached\\' => array($vendorDir . '/league/flysystem-cached-adapter/src'),
     'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'),
     'JmesPath\\' => array($vendorDir . '/mtdowling/jmespath.php/src'),
+    'Http\\Promise\\' => array($vendorDir . '/php-http/promise/src'),
+    'Http\\Message\\' => array($vendorDir . '/php-http/message/src', $vendorDir . '/php-http/message-factory/src'),
+    'Http\\Factory\\Guzzle\\' => array($vendorDir . '/http-interop/http-factory-guzzle/src'),
+    'Http\\Discovery\\' => array($vendorDir . '/php-http/discovery/src'),
+    'Http\\Client\\Common\\' => array($vendorDir . '/php-http/client-common/src'),
+    'Http\\Client\\' => array($vendorDir . '/php-http/httplug/src'),
+    'Http\\Adapter\\Guzzle6\\' => array($vendorDir . '/php-http/guzzle6-adapter/src'),
     'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
     'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
     'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
@@ -62,6 +74,7 @@ return array(
     'EasyWeChat\\' => array($vendorDir . '/overtrue/wechat/src'),
     'Doctrine\\Instantiator\\' => array($vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator'),
     'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'),
+    'Clue\\StreamFilter\\' => array($vendorDir . '/clue/stream-filter/src'),
     'Channel\\' => array($vendorDir . '/workerman/channel/src'),
     'Carbon\\' => array($vendorDir . '/nesbot/carbon/src/Carbon'),
     'Alipay\\EasySDK\\' => array($vendorDir . '/alipaysdk/easysdk/php/src'),

+ 84 - 3
vendor/composer/autoload_static.php

@@ -8,10 +8,10 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
 {
     public static $files = array (
         '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
-        '9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php',
         '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
-        'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
         'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
+        '9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php',
+        'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
         '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
         '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
         '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
@@ -19,8 +19,13 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
         '538ca81a9a966a6716601ecf48f4eaef' => __DIR__ . '/..' . '/opis/closure/functions.php',
         '023d27dca8066ef29e6739335ea73bad' => __DIR__ . '/..' . '/symfony/polyfill-php70/bootstrap.php',
         '65fec9ebcfbb3cbb4fd0d519687aea01' => __DIR__ . '/..' . '/danielstjules/stringy/src/Create.php',
+        'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
+        '9c67151ae59aff4788964ce8eb2a0f43' => __DIR__ . '/..' . '/clue/stream-filter/src/functions_include.php',
         'b067bc7112e384b61c701452d53a14a8' => __DIR__ . '/..' . '/mtdowling/jmespath.php/src/JmesPath.php',
+        '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
         '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
+        '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
+        '8cff32064859f4559445b89279f3199c' => __DIR__ . '/..' . '/php-http/message/src/filters.php',
         '66453932bc1be9fb2f910a27947d11b6' => __DIR__ . '/..' . '/alibabacloud/client/src/Functions.php',
         '6124b4c8570aa390c21fafd04a26c69f' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
         '0d0b82117c23db94c492fee02b2ed01f' => __DIR__ . '/..' . '/songshenzong/support/src/StringsHelpers.php',
@@ -76,6 +81,8 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
         ),
         'S' => 
         array (
+            'Symfony\\Polyfill\\Php80\\' => 23,
+            'Symfony\\Polyfill\\Php73\\' => 23,
             'Symfony\\Polyfill\\Php72\\' => 23,
             'Symfony\\Polyfill\\Php70\\' => 23,
             'Symfony\\Polyfill\\Mbstring\\' => 26,
@@ -84,6 +91,7 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
             'Symfony\\Component\\VarDumper\\' => 28,
             'Symfony\\Component\\Translation\\' => 30,
             'Symfony\\Component\\Process\\' => 26,
+            'Symfony\\Component\\OptionsResolver\\' => 34,
             'Symfony\\Component\\HttpFoundation\\' => 33,
             'Symfony\\Component\\EventDispatcher\\' => 34,
             'Symfony\\Bridge\\PsrHttpMessage\\' => 30,
@@ -104,6 +112,7 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
             'Psr\\SimpleCache\\' => 16,
             'Psr\\Log\\' => 8,
             'Psr\\Http\\Message\\' => 17,
+            'Psr\\Http\\Client\\' => 16,
             'Psr\\Container\\' => 14,
             'Psr\\Cache\\' => 10,
             'Prophecy\\' => 9,
@@ -118,6 +127,7 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
         'M' => 
         array (
             'Monolog\\' => 8,
+            'MeiliSearch\\' => 12,
         ),
         'L' => 
         array (
@@ -128,6 +138,16 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
         array (
             'JmesPath\\' => 9,
         ),
+        'H' => 
+        array (
+            'Http\\Promise\\' => 13,
+            'Http\\Message\\' => 13,
+            'Http\\Factory\\Guzzle\\' => 20,
+            'Http\\Discovery\\' => 15,
+            'Http\\Client\\Common\\' => 19,
+            'Http\\Client\\' => 12,
+            'Http\\Adapter\\Guzzle6\\' => 21,
+        ),
         'G' => 
         array (
             'GuzzleHttp\\Psr7\\' => 16,
@@ -150,6 +170,7 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
         ),
         'C' => 
         array (
+            'Clue\\StreamFilter\\' => 18,
             'Channel\\' => 8,
             'Carbon\\' => 7,
         ),
@@ -245,6 +266,14 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
         array (
             0 => __DIR__ . '/..' . '/webmozart/assert/src',
         ),
+        'Symfony\\Polyfill\\Php80\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
+        ),
+        'Symfony\\Polyfill\\Php73\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/polyfill-php73',
+        ),
         'Symfony\\Polyfill\\Php72\\' => 
         array (
             0 => __DIR__ . '/..' . '/symfony/polyfill-php72',
@@ -277,6 +306,10 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
         array (
             0 => __DIR__ . '/..' . '/symfony/process',
         ),
+        'Symfony\\Component\\OptionsResolver\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/symfony/options-resolver',
+        ),
         'Symfony\\Component\\HttpFoundation\\' => 
         array (
             0 => __DIR__ . '/..' . '/symfony/http-foundation',
@@ -319,7 +352,12 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
         ),
         'Psr\\Http\\Message\\' => 
         array (
-            0 => __DIR__ . '/..' . '/psr/http-message/src',
+            0 => __DIR__ . '/..' . '/psr/http-factory/src',
+            1 => __DIR__ . '/..' . '/psr/http-message/src',
+        ),
+        'Psr\\Http\\Client\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/psr/http-client/src',
         ),
         'Psr\\Container\\' => 
         array (
@@ -353,6 +391,10 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
         array (
             0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog',
         ),
+        'MeiliSearch\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/meilisearch/meilisearch-php/src',
+        ),
         'League\\Flysystem\\Cached\\' => 
         array (
             0 => __DIR__ . '/..' . '/league/flysystem-cached-adapter/src',
@@ -365,6 +407,35 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
         array (
             0 => __DIR__ . '/..' . '/mtdowling/jmespath.php/src',
         ),
+        'Http\\Promise\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/php-http/promise/src',
+        ),
+        'Http\\Message\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/php-http/message/src',
+            1 => __DIR__ . '/..' . '/php-http/message-factory/src',
+        ),
+        'Http\\Factory\\Guzzle\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/http-interop/http-factory-guzzle/src',
+        ),
+        'Http\\Discovery\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/php-http/discovery/src',
+        ),
+        'Http\\Client\\Common\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/php-http/client-common/src',
+        ),
+        'Http\\Client\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/php-http/httplug/src',
+        ),
+        'Http\\Adapter\\Guzzle6\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/php-http/guzzle6-adapter/src',
+        ),
         'GuzzleHttp\\Psr7\\' => 
         array (
             0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
@@ -397,6 +468,10 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
         array (
             0 => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy',
         ),
+        'Clue\\StreamFilter\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/clue/stream-filter/src',
+        ),
         'Channel\\' => 
         array (
             0 => __DIR__ . '/..' . '/workerman/channel/src',
@@ -477,9 +552,11 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
     public static $classMap = array (
         'ArithmeticError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/ArithmeticError.php',
         'AssertionError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/AssertionError.php',
+        'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
         'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
         'DivisionByZeroError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php',
         'Error' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/Error.php',
+        'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
         'PHPUnit\\Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Exception.php',
         'PHPUnit\\Framework\\Assert' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Assert.php',
         'PHPUnit\\Framework\\AssertionFailedError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception/AssertionFailedError.php',
@@ -941,6 +1018,7 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
         'PharIo\\Version\\VersionConstraintParser' => __DIR__ . '/..' . '/phar-io/version/src/VersionConstraintParser.php',
         'PharIo\\Version\\VersionConstraintValue' => __DIR__ . '/..' . '/phar-io/version/src/VersionConstraintValue.php',
         'PharIo\\Version\\VersionNumber' => __DIR__ . '/..' . '/phar-io/version/src/VersionNumber.php',
+        'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
         'SebastianBergmann\\CodeCoverage\\CodeCoverage' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage.php',
         'SebastianBergmann\\CodeCoverage\\CoveredCodeNotExecutedException' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Exception/CoveredCodeNotExecutedException.php',
         'SebastianBergmann\\CodeCoverage\\Driver\\Driver' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/Driver/Driver.php',
@@ -1054,6 +1132,7 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
         'SebastianBergmann\\Type\\VoidType' => __DIR__ . '/..' . '/sebastian/type/src/VoidType.php',
         'SebastianBergmann\\Version' => __DIR__ . '/..' . '/sebastian/version/src/Version.php',
         'SessionUpdateTimestampHandlerInterface' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/SessionUpdateTimestampHandlerInterface.php',
+        'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
         'Text_Template' => __DIR__ . '/..' . '/phpunit/php-text-template/src/Template.php',
         'TheSeer\\Tokenizer\\Exception' => __DIR__ . '/..' . '/theseer/tokenizer/src/Exception.php',
         'TheSeer\\Tokenizer\\NamespaceUri' => __DIR__ . '/..' . '/theseer/tokenizer/src/NamespaceUri.php',
@@ -1064,6 +1143,8 @@ class ComposerStaticInitf16474ac994ccc25392f403933800b79
         'TheSeer\\Tokenizer\\Tokenizer' => __DIR__ . '/..' . '/theseer/tokenizer/src/Tokenizer.php',
         'TheSeer\\Tokenizer\\XMLSerializer' => __DIR__ . '/..' . '/theseer/tokenizer/src/XMLSerializer.php',
         'TypeError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/TypeError.php',
+        'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
+        'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
     );
 
     public static function getInitializer(ClassLoader $loader)

Diferenças do arquivo suprimidas por serem muito extensas
+ 813 - 84
vendor/composer/installed.json


+ 176 - 2
vendor/composer/installed.php

@@ -5,7 +5,7 @@
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
-        'reference' => 'cd004333264e0f0247ac29635942f3ab55b42a60',
+        'reference' => '897ff510ac9cfe4f8d456fc5fceb1347431b47ea',
         'name' => 'topthink/think',
         'dev' => true,
     ),
@@ -82,6 +82,15 @@
             'reference' => 'ecf584f5b3a27929175ff0abdba52f0131bef795',
             'dev_requirement' => false,
         ),
+        'clue/stream-filter' => array(
+            'pretty_version' => 'v1.6.0',
+            'version' => '1.6.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../clue/stream-filter',
+            'aliases' => array(),
+            'reference' => 'd6169430c7731d8509da7aecd0af756a5747b78e',
+            'dev_requirement' => false,
+        ),
         'danielstjules/stringy' => array(
             'pretty_version' => '3.1.0',
             'version' => '3.1.0.0',
@@ -295,6 +304,15 @@
             'reference' => '9f83dded91781a01c63574e387eaa769be769115',
             'dev_requirement' => false,
         ),
+        'http-interop/http-factory-guzzle' => array(
+            'pretty_version' => '1.1.1',
+            'version' => '1.1.1.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../http-interop/http-factory-guzzle',
+            'aliases' => array(),
+            'reference' => '6e1efa1e020bf1c47cf0f13654e8ef9efb1463b3',
+            'dev_requirement' => false,
+        ),
         'league/flysystem' => array(
             'pretty_version' => '1.0.57',
             'version' => '1.0.57.0',
@@ -313,6 +331,15 @@
             'reference' => '08ef74e9be88100807a3b92cc9048a312bf01d6f',
             'dev_requirement' => false,
         ),
+        'meilisearch/meilisearch-php' => array(
+            'pretty_version' => 'v0.17.2',
+            'version' => '0.17.2.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../meilisearch/meilisearch-php',
+            'aliases' => array(),
+            'reference' => 'a200a32093ae44c04523f6fd014ec888707ceb9e',
+            'dev_requirement' => false,
+        ),
         'monolog/monolog' => array(
             'pretty_version' => '1.24.0',
             'version' => '1.24.0.0',
@@ -415,6 +442,87 @@
             'reference' => 'bae7c545bef187884426f042434e561ab1ddb182',
             'dev_requirement' => true,
         ),
+        'php-http/async-client-implementation' => array(
+            'dev_requirement' => false,
+            'provided' => array(
+                0 => '1.0',
+            ),
+        ),
+        'php-http/client-common' => array(
+            'pretty_version' => '2.5.0',
+            'version' => '2.5.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../php-http/client-common',
+            'aliases' => array(),
+            'reference' => 'd135751167d57e27c74de674d6a30cef2dc8e054',
+            'dev_requirement' => false,
+        ),
+        'php-http/client-implementation' => array(
+            'dev_requirement' => false,
+            'provided' => array(
+                0 => '1.0',
+            ),
+        ),
+        'php-http/discovery' => array(
+            'pretty_version' => '1.14.1',
+            'version' => '1.14.1.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../php-http/discovery',
+            'aliases' => array(),
+            'reference' => 'de90ab2b41d7d61609f504e031339776bc8c7223',
+            'dev_requirement' => false,
+        ),
+        'php-http/guzzle6-adapter' => array(
+            'pretty_version' => 'v2.0.2',
+            'version' => '2.0.2.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../php-http/guzzle6-adapter',
+            'aliases' => array(),
+            'reference' => '9d1a45eb1c59f12574552e81fb295e9e53430a56',
+            'dev_requirement' => false,
+        ),
+        'php-http/httplug' => array(
+            'pretty_version' => '2.3.0',
+            'version' => '2.3.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../php-http/httplug',
+            'aliases' => array(),
+            'reference' => 'f640739f80dfa1152533976e3c112477f69274eb',
+            'dev_requirement' => false,
+        ),
+        'php-http/message' => array(
+            'pretty_version' => '1.13.0',
+            'version' => '1.13.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../php-http/message',
+            'aliases' => array(),
+            'reference' => '7886e647a30a966a1a8d1dad1845b71ca8678361',
+            'dev_requirement' => false,
+        ),
+        'php-http/message-factory' => array(
+            'pretty_version' => 'v1.0.2',
+            'version' => '1.0.2.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../php-http/message-factory',
+            'aliases' => array(),
+            'reference' => 'a478cb11f66a6ac48d8954216cfed9aa06a501a1',
+            'dev_requirement' => false,
+        ),
+        'php-http/message-factory-implementation' => array(
+            'dev_requirement' => false,
+            'provided' => array(
+                0 => '1.0',
+            ),
+        ),
+        'php-http/promise' => array(
+            'pretty_version' => '1.1.0',
+            'version' => '1.1.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../php-http/promise',
+            'aliases' => array(),
+            'reference' => '4c4c1f9b7289a2ec57cde7f1e9762a5789506f88',
+            'dev_requirement' => false,
+        ),
         'phpdocumentor/reflection-common' => array(
             'pretty_version' => '2.2.0',
             'version' => '2.2.0.0',
@@ -541,6 +649,36 @@
             'reference' => 'b7ce3b176482dbbc1245ebf52b181af44c2cf55f',
             'dev_requirement' => false,
         ),
+        'psr/http-client' => array(
+            'pretty_version' => '1.0.1',
+            'version' => '1.0.1.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../psr/http-client',
+            'aliases' => array(),
+            'reference' => '2dfb5f6c5eff0e91e20e913f8c5452ed95b86621',
+            'dev_requirement' => false,
+        ),
+        'psr/http-client-implementation' => array(
+            'dev_requirement' => false,
+            'provided' => array(
+                0 => '1.0',
+            ),
+        ),
+        'psr/http-factory' => array(
+            'pretty_version' => '1.0.1',
+            'version' => '1.0.1.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../psr/http-factory',
+            'aliases' => array(),
+            'reference' => '12ac7fcd07e5b077433f5f2bee95b3a771bf61be',
+            'dev_requirement' => false,
+        ),
+        'psr/http-factory-implementation' => array(
+            'dev_requirement' => false,
+            'provided' => array(
+                0 => '^1.0',
+            ),
+        ),
         'psr/http-message' => array(
             'pretty_version' => '1.0.1',
             'version' => '1.0.1.0',
@@ -742,6 +880,15 @@
             'reference' => '74b0d189ce75142f1706aad834d5a428dfc7c3c3',
             'dev_requirement' => false,
         ),
+        'symfony/deprecation-contracts' => array(
+            'pretty_version' => 'v2.5.0',
+            'version' => '2.5.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
+            'aliases' => array(),
+            'reference' => '6f981ee24cf69ee7ce9736146d1c57c2780598a8',
+            'dev_requirement' => false,
+        ),
         'symfony/event-dispatcher' => array(
             'pretty_version' => 'v2.8.50',
             'version' => '2.8.50.0',
@@ -760,6 +907,15 @@
             'reference' => '677ae5e892b081e71a665bfa7dd90fe61800c00e',
             'dev_requirement' => false,
         ),
+        'symfony/options-resolver' => array(
+            'pretty_version' => 'v5.4.3',
+            'version' => '5.4.3.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../symfony/options-resolver',
+            'aliases' => array(),
+            'reference' => 'cc1147cb11af1b43f503ac18f31aa3bec213aba8',
+            'dev_requirement' => false,
+        ),
         'symfony/polyfill-ctype' => array(
             'pretty_version' => 'v1.23.0',
             'version' => '1.23.0.0',
@@ -796,6 +952,24 @@
             'reference' => 'ab50dcf166d5f577978419edd37aa2bb8eabce0c',
             'dev_requirement' => false,
         ),
+        'symfony/polyfill-php73' => array(
+            'pretty_version' => 'v1.25.0',
+            'version' => '1.25.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../symfony/polyfill-php73',
+            'aliases' => array(),
+            'reference' => 'cc5db0e22b3cb4111010e48785a97f670b350ca5',
+            'dev_requirement' => false,
+        ),
+        'symfony/polyfill-php80' => array(
+            'pretty_version' => 'v1.25.0',
+            'version' => '1.25.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../symfony/polyfill-php80',
+            'aliases' => array(),
+            'reference' => '4407588e0d3f1f52efb65fbe92babe41f37fe50c',
+            'dev_requirement' => false,
+        ),
         'symfony/process' => array(
             'pretty_version' => 'v4.3.2',
             'version' => '4.3.2.0',
@@ -871,7 +1045,7 @@
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
-            'reference' => 'cd004333264e0f0247ac29635942f3ab55b42a60',
+            'reference' => '897ff510ac9cfe4f8d456fc5fceb1347431b47ea',
             'dev_requirement' => false,
         ),
         'topthink/think-captcha' => array(

+ 2 - 2
vendor/composer/platform_check.php

@@ -4,8 +4,8 @@
 
 $issues = array();
 
-if (!(PHP_VERSION_ID >= 70108)) {
-    $issues[] = 'Your Composer dependencies require a PHP version ">= 7.1.8". You are running ' . PHP_VERSION . '.';
+if (!(PHP_VERSION_ID >= 70205)) {
+    $issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.5". You are running ' . PHP_VERSION . '.';
 }
 
 if ($issues) {

+ 21 - 0
vendor/http-interop/http-factory-guzzle/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Woody Gilk
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 3 - 0
vendor/http-interop/http-factory-guzzle/README.md

@@ -0,0 +1,3 @@
+# HTTP Factory for Guzzle
+
+HTTP factory implemented for [Guzzle](https://github.com/guzzle/psr7).

+ 33 - 0
vendor/http-interop/http-factory-guzzle/composer.json

@@ -0,0 +1,33 @@
+{
+    "name": "http-interop/http-factory-guzzle",
+    "description": "An HTTP Factory using Guzzle PSR7",
+    "keywords": [
+        "psr-7",
+        "psr-17",
+        "http",
+        "factory"
+    ],
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "PHP-FIG",
+            "homepage": "http://www.php-fig.org/"
+        }
+    ],
+    "provide": {
+        "psr/http-factory-implementation": "^1.0"
+    },
+    "require": {
+        "psr/http-factory": "^1.0",
+        "guzzlehttp/psr7": "^1.4.2||^2.0"
+    },
+    "require-dev": {
+        "http-interop/http-factory-tests": "^0.9",
+        "phpunit/phpunit": "^8.5"
+    },
+    "autoload": {
+        "psr-4": {
+            "Http\\Factory\\Guzzle\\": "src/"
+        }
+    }
+}

+ 15 - 0
vendor/http-interop/http-factory-guzzle/src/RequestFactory.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace Http\Factory\Guzzle;
+
+use GuzzleHttp\Psr7\Request;
+use Psr\Http\Message\RequestFactoryInterface;
+use Psr\Http\Message\RequestInterface;
+
+class RequestFactory implements RequestFactoryInterface
+{
+    public function createRequest(string $method, $uri): RequestInterface
+    {
+        return new Request($method, $uri);
+    }
+}

+ 15 - 0
vendor/http-interop/http-factory-guzzle/src/ResponseFactory.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace Http\Factory\Guzzle;
+
+use GuzzleHttp\Psr7\Response;
+use Psr\Http\Message\ResponseFactoryInterface;
+use Psr\Http\Message\ResponseInterface;
+
+class ResponseFactory implements ResponseFactoryInterface
+{
+    public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
+    {
+        return new Response($code, [], null, '1.1', $reasonPhrase);
+    }
+}

+ 24 - 0
vendor/http-interop/http-factory-guzzle/src/ServerRequestFactory.php

@@ -0,0 +1,24 @@
+<?php
+
+namespace Http\Factory\Guzzle;
+
+use GuzzleHttp\Psr7\ServerRequest;
+use Psr\Http\Message\ServerRequestFactoryInterface;
+use Psr\Http\Message\ServerRequestInterface;
+
+class ServerRequestFactory implements ServerRequestFactoryInterface
+{
+    public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
+    {
+
+        if (empty($method)) {
+            if (!empty($serverParams['REQUEST_METHOD'])) {
+                $method = $serverParams['REQUEST_METHOD'];
+            } else {
+                throw new \InvalidArgumentException('Cannot determine HTTP method');
+            }
+        }
+
+        return new ServerRequest($method, $uri, [], null, '1.1', $serverParams);
+    }
+}

+ 43 - 0
vendor/http-interop/http-factory-guzzle/src/StreamFactory.php

@@ -0,0 +1,43 @@
+<?php
+
+namespace Http\Factory\Guzzle;
+
+use GuzzleHttp\Psr7\Stream;
+use GuzzleHttp\Psr7\Utils;
+use Psr\Http\Message\StreamFactoryInterface;
+use Psr\Http\Message\StreamInterface;
+
+use function function_exists;
+use function GuzzleHttp\Psr7\stream_for;
+use function GuzzleHttp\Psr7\try_fopen;
+
+class StreamFactory implements StreamFactoryInterface
+{
+    public function createStream(string $content = ''): StreamInterface
+    {
+        if (function_exists('GuzzleHttp\Psr7\stream_for')) {
+            // fallback for guzzlehttp/psr7<1.7.0
+            return stream_for($content);
+        }
+
+        return Utils::streamFor($content);
+    }
+
+    public function createStreamFromFile(string $file, string $mode = 'r'): StreamInterface
+    {
+        if (function_exists('GuzzleHttp\Psr7\try_fopen')) {
+            // fallback for guzzlehttp/psr7<1.7.0
+            $resource = try_fopen($file, $mode);
+        } else {
+            $resource = Utils::tryFopen($file, $mode);
+        }
+
+
+        return $this->createStreamFromResource($resource);
+    }
+
+    public function createStreamFromResource($resource): StreamInterface
+    {
+        return new Stream($resource);
+    }
+}

+ 25 - 0
vendor/http-interop/http-factory-guzzle/src/UploadedFileFactory.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Http\Factory\Guzzle;
+
+use GuzzleHttp\Psr7\UploadedFile;
+use Psr\Http\Message\UploadedFileFactoryInterface;
+use Psr\Http\Message\StreamInterface;
+use Psr\Http\Message\UploadedFileInterface;
+
+class UploadedFileFactory implements UploadedFileFactoryInterface
+{
+    public function createUploadedFile(
+        StreamInterface $stream,
+        int $size = null,
+        int $error = \UPLOAD_ERR_OK,
+        string $clientFilename = null,
+        string $clientMediaType = null
+    ): UploadedFileInterface {
+        if ($size === null) {
+            $size = $stream->getSize();
+        }
+
+        return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType);
+    }
+}

+ 15 - 0
vendor/http-interop/http-factory-guzzle/src/UriFactory.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace Http\Factory\Guzzle;
+
+use GuzzleHttp\Psr7\Uri;
+use Psr\Http\Message\UriFactoryInterface;
+use Psr\Http\Message\UriInterface;
+
+class UriFactory implements UriFactoryInterface
+{
+    public function createUri(string $uri = ''): UriInterface
+    {
+        return new Uri($uri);
+    }
+}

+ 309 - 0
vendor/meilisearch/meilisearch-php/.code-samples.meilisearch.yaml

@@ -0,0 +1,309 @@
+# This code-samples file is used by the MeiliSearch documentation
+# Every example written here will be automatically fetched by
+# the documentation on build
+# You can read more on https://github.com/meilisearch/documentation/tree/master/.vuepress/code-samples
+---
+get_one_index_1: |-
+  $client->index('movies')->fetchRawInfo();
+list_all_indexes_1: |-
+  $client->getAllIndexes();
+create_an_index_1: |-
+  $client->createIndex('movies', ['primaryKey' => 'movie_id']);
+update_an_index_1: |-
+  $client->updateIndex('movies', ['primaryKey' => 'movie_id']);
+  // OR
+  $client->index('movies')->update(['primaryKey' => 'movie_id']);
+delete_an_index_1: |-
+  $client->deleteIndex('movies');
+  // OR
+  $client->index('movies')->delete();
+get_one_document_1: |-
+  $client->index('movies')->getDocument(25684);
+get_documents_1: |-
+  $client->index('movies')->getDocuments(['limit' => 2]);
+add_or_replace_documents_1: |-
+  $client->index('movies')->addDocuments([
+    [
+      'id' => 287947
+      'title' => 'Shazam',
+      'poster' => 'https://image.tmdb.org/t/p/w1280/xnopI5Xtky18MPhK40cZAGAOVeV.jpg',
+      'overview' => 'A boy is given the ability to become an adult superhero in times of need with a single magic word.',
+      'release_date' => '2019-03-23'
+    ]
+  ]);
+add_or_update_documents_1: |-
+  $client->index('movies')->updateDocuments([
+    [
+      'id' => 287947
+      'title' => 'Shazam ⚡️',
+      'genres' => 'comedy'
+    ]
+  ]);
+delete_all_documents_1: |-
+  $client->index('movies')->deleteAllDocuments();
+delete_one_document_1: |-
+  $client->index('movies')->deleteDocument(25684);
+delete_documents_1: |-
+  $client->index('movies')->deleteDocuments([23488, 153738, 437035, 363869]);
+search_1: |-
+  // Do a search
+  $searchResults = $client->index('movies')->search('american ninja');
+
+  // Get results in an Array using a getter
+  $hits = $searchResults->getHits();
+
+  // Get the decoded response of MeiliSearch, see response below
+  $response = $searchResults->getRaw();
+get_update_1: |-
+  $client->index('movies')->getUpdateStatus(1);
+get_all_updates_1: |-
+  $client->index('movies')->getAllUpdateStatus();
+get_keys_1: |-
+  $client->getKeys();
+get_settings_1: |-
+  $client->index('movies')->getSettings();
+update_settings_1: |-
+  $client->index('movies')->updateSettings([
+    'rankingRules' => [
+      'typo',
+      'words',
+      'proximity',
+      'attribute',
+      'wordsPosition',
+      'exactness',
+      'desc(release_date)',
+      'desc(rank)'
+    ],
+    'distinctAttribute' => 'movie_id',
+    'searchableAttributes' => [
+      'title',
+      'description',
+      'genre'
+    ],
+    'displayedAttributes' => [
+      'title',
+      'description',
+      'genre',
+      'release_date'
+    ],
+    'stopWords' => [
+      'the',
+      'a',
+      'an'
+    ],
+    'synonyms' => [
+      'wolverine': ['xmen', 'logan'],
+      'logan': ['wolverine']
+    ]
+  ]);
+reset_settings_1: |-
+  $client->index('movies')->resetSettings();
+get_synonyms_1: |-
+  $client->index('movies')->getSynonyms();
+update_synonyms_1: |-
+  $client->index('movies')->updateSynonyms([
+    'wolverine': ['xmen', 'logan'],
+    'logan': ['wolverine', 'xmen'],
+    'wow': ['world of warcraft']
+  ]);
+reset_synonyms_1: |-
+  $client->index('movies')->resetSynonyms();
+get_stop_words_1: |-
+  $client->index('movies')->getStopWords();
+update_stop_words_1: |-
+  $client->index('movies')->updateStopWords(['the', 'of', 'to']);
+reset_stop_words_1: |-
+  $client->index('movies')->resetStopWords();
+get_ranking_rules_1: |-
+  $client->index('movies')->getRankingRules();
+update_ranking_rules_1: |-
+  $client->index('movies')->updateRankingRules([
+    'typo',
+    'words',
+    'proximity',
+    'attribute',
+    'wordsPosition',
+    'exactness',
+    'asc(release_date)',
+    'desc(rank)'
+  ]);
+reset_ranking_rules_1: |-
+  $client->index('movies')->resetRankingRules();
+get_distinct_attribute_1: |-
+  $client->index('movies')->getDistinctAttribute();
+update_distinct_attribute_1: |-
+  $client->index('movies')->updateDistinctAttribute('movie_id');
+reset_distinct_attribute_1: |-
+  $client->index('movies')->resetDistinctAttribute();
+get_searchable_attributes_1: |-
+  $client->index('movies')->getSearchableAttributes();
+update_searchable_attributes_1: |-
+  $client->index('movies')->updateSearchableAttributes([
+    'title',
+    'description',
+    'genre'
+  ]);
+reset_searchable_attributes_1: |-
+  $client->index('movies')->resetSearchableAttributes();
+get_attributes_for_faceting_1: |-
+  $client->index('movies')->getAttributesForFaceting();
+update_attributes_for_faceting_1: |-
+  $client->index('movies')->updateAttributesForFaceting([
+    'genres',
+    'director'
+  ]);
+reset_attributes_for_faceting_1: |-
+  $client->index('movies')->resetAttributesForFaceting();
+get_displayed_attributes_1: |-
+  $client->index('movies')->getDisplayedAttributes();
+update_displayed_attributes_1: |-
+  $client->index('movies')->updateDisplayedAttributes([
+    'title',
+    'description',
+    'genre',
+    'release_date'
+  ]);
+reset_displayed_attributes_1: |-
+  $client->index('movies')->resetDisplayedAttributes();
+get_index_stats_1: |-
+  $client->index('movies')->stats();
+get_indexes_stats_1: |-
+  $client->stats();
+get_health_1: |-
+  $client->health();
+get_version_1: |-
+  $client->version();
+distinct_attribute_guide_1: |-
+  $client->index('jackets')->updateDistinctAttribute('product_id');
+field_properties_guide_searchable_1: |-
+  $client->index('movies')->updateSearchableAttributes([
+    'title',
+    'description',
+    'genre'
+  ]);
+field_properties_guide_displayed_1: |-
+  $client->index('movies')->updateDisplayedAttributes([
+    'title',
+    'description',
+    'genre',
+    'release_date'
+  ]);
+filtering_guide_1: |-
+  $client->index('movies')->search('Avengers', ['filters' => 'release_date > 795484800']);
+filtering_guide_2: |-
+  $client->index('movies')->search('Avengers', ['filters' => 'release_date > 795484800 AND (director = "Tim Burton" OR director = "Christopher Nolan")']);
+filtering_guide_3: |-
+  $client->index('movies')->search('horror', ['filters' => 'director = "Jordan Peele"']);
+filtering_guide_4: |-
+  $client->index('movies')->search('Planet of the Apes', ['filters' => 'rating >= 3 AND (NOT director = "Tim Burton")']);
+search_parameter_guide_query_1: |-
+  $client->index('movies')->search('shifu');
+search_parameter_guide_offset_1: |-
+  $client->index('movies')->search('shifu', ['offset' => 1]);
+search_parameter_guide_limit_1: |-
+  $client->index('movies')->search('shifu', ['limit' => 1]);
+search_parameter_guide_retrieve_1: |-
+  $client->index('movies')->search('shifu', ['attributesToRetrieve' => ['overview', 'title']]);
+search_parameter_guide_crop_1: |-
+  $client->index('movies')->search('shifu', ['attributesToCrop' => ['overview'], 'cropLength' => 10]);
+search_parameter_guide_highlight_1: |-
+  $client->index('movies')->search('shifu', ['attributesToHighlight' => ['overview']]);
+search_parameter_guide_filter_1: |-
+  $client->index('movies')->search('n', ['filters' => 'title = Nightshift']);
+search_parameter_guide_filter_2: |-
+  $client->index('movies')->search('shifu', ['filters' => 'title="Kung Fu Panda"']);
+search_parameter_guide_matches_1: |-
+  $client->index('movies')->search('shifu', ['attributesToHighlight' => ['overview'], 'matches' => true]);
+settings_guide_synonyms_1: |-
+  $client->index('tops')->updateSynonyms(['sweater' => ['jumper'], 'jumper' => ['sweater']]);
+settings_guide_stop_words_1: |-
+  $client->index('movies')->updateStopWords(['the', 'a', 'an']);
+settings_guide_ranking_rules_1: |-
+  $client->index('movies')->updateRankingRules([
+    'typo',
+    'words',
+    'proximity',
+    'attribute',
+    'wordsPosition',
+    'exactness',
+    'asc(release_date)',
+    'desc(rank)'
+  ]);
+settings_guide_distinct_1: |-
+  $client->index('jackets')->updateDistinctAttribute('product_id');
+settings_guide_searchable_1: |-
+  $client->index('movies')->updateSearchableAttributes([
+    'title',
+    'description',
+    'genre'
+  ]);
+settings_guide_displayed_1: |-
+  $client->index('movies')->updateDisplayedAttributes([
+    'title',
+    'description',
+    'genre',
+    'release_date'
+  ]);
+add_movies_json_1: |-
+  $moviesJson = file_get_contents('movies.json');
+  $movies = json_decode($moviesJson);
+
+  $client->index('movies')->addDocuments($movies)
+documents_guide_add_movie_1: |-
+  $client->index('movies')->addDocuments([['movie_id' => '123sq178', 'title' => 'Amelie Poulain']]);
+search_guide_1: |-
+  $client->index('movies')->search('shifu', ['limit' => 5, 'offset' => 10]);
+search_guide_2: |-
+  $client->index('movies')->search('Avengers', ['filters' => 'release_date > 795484800']);
+getting_started_add_documents_md: |-
+  Using `meilisearch-php` with the Guzzle HTTP client:
+
+  ```bash
+  composer require meilisearch/meilisearch-php \
+      guzzlehttp/guzzle \
+      http-interop/http-factory-guzzle:^1.0
+  ```
+
+  ```php
+  <?php
+
+  require_once __DIR__ . '/vendor/autoload.php';
+
+  use MeiliSearch\Client;
+
+  $client = new Client('http://127.0.0.1:7700');
+
+  $movies_json = file_get_contents('movies.json');
+  $movies = json_decode($movies_json);
+
+  $client->index('movies')->addDocuments($movies);
+  ```
+
+  [About this SDK](https://github.com/meilisearch/meilisearch-php/)
+getting_started_search_md: |-
+  ```php
+  $index->search('botman');
+  ```
+
+  [About this SDK](https://github.com/meilisearch/meilisearch-php/)
+faceted_search_update_settings_1: |-
+  $client->index('movies')->updateAttributesForFaceting(['director', 'genres']);
+faceted_search_facet_filters_1: |-
+  $client->index('movies')->search('thriller', ['facetFilters' => [['genres:Horror', 'genres:Mystery']], 'director' => "Jordan Peele"']);
+faceted_search_facets_distribution_1: |-
+  $client->index('movies')->search('Batman', ['facetsDistribution' => ['genres']]);
+faceted_search_walkthrough_attributes_for_faceting_1: |-
+  $client->index('movies')->updateAttributesForFaceting([
+    'director',
+    'producer',
+    'genres',
+    'production_companies'
+  ]);
+faceted_search_walkthrough_facet_filters_1: |-
+  $client->index('movies')->search('thriller', ['facetFilters' => [['genres:Horror', 'genres:Mystery']], 'director' => "Jordan Peele"]);
+faceted_search_walkthrough_facets_distribution_1: |-
+  $client->index('movies')->search('Batman', ['facetsDistribution' => ['genres']);
+post_dump_1: |-
+  $client->createDump();
+get_dump_status_1: |-
+  $client->getDumpStatus('20201101-110357260');

+ 20 - 0
vendor/meilisearch/meilisearch-php/.editorconfig

@@ -0,0 +1,20 @@
+# https://editorconfig.org/
+
+root = true
+
+[*]
+trim_trailing_whitespace = true
+insert_final_newline     = true
+end_of_line              = lf
+charset                  = utf-8
+tab_width                = 4
+indent_style             = space
+
+[*.php]
+indent_size              = 4
+
+[*.md]
+max_line_length          = 80
+
+[*.{yaml,yml}]
+indent_size              = 2

+ 27 - 0
vendor/meilisearch/meilisearch-php/.github/release-draft-template.yml

@@ -0,0 +1,27 @@
+name-template: 'v$RESOLVED_VERSION 🐘'
+tag-template: 'v$RESOLVED_VERSION'
+exclude-labels:
+  - 'skip-changelog'
+version-resolver:
+  minor:
+    labels:
+      - 'breaking-change'
+  default: patch
+categories:
+  - title: 'Breaking changes ⚠️'
+    label: 'breaking-change'
+template: |
+  ## Changes
+
+  $CHANGES
+
+  Thanks again to $CONTRIBUTORS! 🎉
+no-changes-template: 'Changes are coming soon 😎'
+sort-direction: 'ascending'
+replacers:
+  - search: '/(?:and )?@dependabot-preview(?:\[bot\])?,?/g'
+    replace: ''
+  - search: '/(?:and )?@bors(?:\[bot\])?,?/g'
+    replace: ''
+  - search: '/(?:and )?@meili-bot,?/g'
+    replace: ''

+ 66 - 0
vendor/meilisearch/meilisearch-php/.github/workflows/pre-release-tests.yml

@@ -0,0 +1,66 @@
+# Testing the code base against the MeiliSearch pre-releases
+name: Pre-Release Tests
+
+# Will only run for PRs and pushes to bump-meilisearch-v*
+on:
+  push:
+    branches: bump-meilisearch-v*
+  pull_request:
+    branches: bump-meilisearch-v*
+
+jobs:
+  tests:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        php-versions: ['7.2', '7.3', '7.4', '8.0']
+    name: integration-tests-against-rc (PHP ${{ matrix.php-versions }})
+    steps:
+    - uses: actions/checkout@v1
+    - name: Install PHP
+      uses: shivammathur/setup-php@v2
+      with:
+        php-version: ${{ matrix.php-versions }}
+    - name: Validate composer.json and composer.lock
+      run: composer validate
+    - name: Install dependencies
+      run: |
+        composer remove --dev friendsofphp/php-cs-fixer --no-update --no-interaction
+        composer install --prefer-dist --no-progress --no-suggest
+    - name: Get the latest MeiliSearch RC
+      run: echo "MEILISEARCH_VERSION=$(curl https://raw.githubusercontent.com/meilisearch/integration-guides/main/scripts/get-latest-meilisearch-rc.sh | bash)" >> $GITHUB_ENV
+    - name: MeiliSearch (${{ env.MEILISEARCH_VERSION }}) setup with Docker
+      run: docker run -d -p 7700:7700 getmeili/meilisearch:${{ env.MEILISEARCH_VERSION }} ./meilisearch --master-key=masterKey --no-analytics=true
+    - name: Run test suite - default HTTP client (Guzzle 7)
+      run: |
+        sh scripts/tests.sh
+        composer remove --dev guzzlehttp/guzzle http-interop/http-factory-guzzle
+    - name: Run test suite - php-http/guzzle6-adapter
+      # Don't run on PHP 8
+      # Guzzle 6 is not compatible with PHP 8.0
+      if: ${{ !startsWith(matrix.php-versions, '8.') }}
+      run: |
+        composer require --dev php-http/guzzle6-adapter http-interop/http-factory-guzzle
+        sh scripts/tests.sh
+        composer remove --dev php-http/guzzle6-adapter http-interop/http-factory-guzzle
+    - name: Run test suite - symfony/http-client
+      run: |
+        composer require --dev symfony/http-client nyholm/psr7
+        sh scripts/tests.sh
+        composer remove --dev symfony/http-client nyholm/psr7
+    - name: Run test suite - php-http/curl-client
+      # Don't run on PHP 8
+      # php-http/curl-client is currently not compatible with PHP 8.0
+      if: ${{ !startsWith(matrix.php-versions, '8.') }}
+      run: |
+        composer require --dev php-http/curl-client nyholm/psr7
+        sh scripts/tests.sh
+        composer remove --dev php-http/curl-client nyholm/psr7
+    - name: Run test suite - kriswallsmith/buzz
+      # Don't run on PHP 8
+      # kriswallsmith/buzz is currently not compatible with PHP 8.0
+      if: ${{ !startsWith(matrix.php-versions, '8.') }}
+      run: |
+        composer require --dev kriswallsmith/buzz nyholm/psr7
+        sh scripts/tests.sh
+        composer remove --dev kriswallsmith/buzz nyholm/psr7

+ 16 - 0
vendor/meilisearch/meilisearch-php/.github/workflows/release-drafter.yml

@@ -0,0 +1,16 @@
+name: Release Drafter
+
+on:
+  push:
+    branches:
+      - main
+
+jobs:
+  update_release_draft:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: release-drafter/release-drafter@v5
+        with:
+          config-name: release-draft-template.yml
+        env:
+          GITHUB_TOKEN: ${{ secrets.RELEASE_DRAFTER_TOKEN }}

+ 82 - 0
vendor/meilisearch/meilisearch-php/.github/workflows/tests.yml

@@ -0,0 +1,82 @@
+name: Tests
+
+on:
+  pull_request:
+  push:
+    # trying and staging branches are for BORS config
+    branches:
+      - trying
+      - staging
+      - main
+
+jobs:
+  lint:
+    runs-on: ubuntu-latest
+    name: linter-check
+    steps:
+      - uses: actions/checkout@v1
+      - name: Install PHP
+        uses: shivammathur/setup-php@v2
+        with:
+          php-version: 7.4
+      - name: Install dependencies
+        run: composer update --prefer-dist --no-progress
+      - name: Run linter
+        run: vendor/bin/php-cs-fixer fix -v --config=.php_cs.dist --using-cache=no --dry-run --allow-risky=yes
+
+  tests:
+    # Will not run if the event is a PR to bump-meilisearch-v* (so a pre-release PR)
+    # Will still run for each push to bump-meilisearch-v*
+    if: github.event_name != 'pull_request' || !startsWith(github.base_ref, 'bump-meilisearch-v')
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        php-versions: ['7.2', '7.3', '7.4', '8.0']
+    name: integration-tests (PHP ${{ matrix.php-versions }})
+    steps:
+    - uses: actions/checkout@v1
+    - name: Install PHP
+      uses: shivammathur/setup-php@v2
+      with:
+        php-version: ${{ matrix.php-versions }}
+    - name: Validate composer.json and composer.lock
+      run: composer validate
+    - name: Install dependencies
+      run: |
+        composer remove --dev friendsofphp/php-cs-fixer --no-update --no-interaction
+        composer update --prefer-dist --no-progress
+    - name: MeiliSearch (latest version) setup with Docker
+      run: docker run -d -p 7700:7700 getmeili/meilisearch:latest ./meilisearch --master-key=masterKey --no-analytics=true
+    - name: Run test suite - default HTTP client (Guzzle 7)
+      run: |
+        sh scripts/tests.sh
+        composer remove --dev guzzlehttp/guzzle http-interop/http-factory-guzzle
+    - name: Run test suite - php-http/guzzle6-adapter
+      # Don't run on PHP 8
+      # Guzzle 6 is not compatible with PHP 8.0
+      if: ${{ !startsWith(matrix.php-versions, '8.') }}
+      run: |
+        composer require --dev php-http/guzzle6-adapter http-interop/http-factory-guzzle
+        sh scripts/tests.sh
+        composer remove --dev php-http/guzzle6-adapter http-interop/http-factory-guzzle
+    - name: Run test suite - symfony/http-client
+      run: |
+        composer require --dev symfony/http-client nyholm/psr7
+        sh scripts/tests.sh
+        composer remove --dev symfony/http-client nyholm/psr7
+    - name: Run test suite - php-http/curl-client
+      # Don't run on PHP 8
+      # php-http/curl-client is currently not compatible with PHP 8.0
+      if: ${{ !startsWith(matrix.php-versions, '8.') }}
+      run: |
+        composer require --dev php-http/curl-client nyholm/psr7
+        sh scripts/tests.sh
+        composer remove --dev php-http/curl-client nyholm/psr7
+    - name: Run test suite - kriswallsmith/buzz
+      # Don't run on PHP 8
+      # kriswallsmith/buzz is currently not compatible with PHP 8.0
+      if: ${{ !startsWith(matrix.php-versions, '8.') }}
+      run: |
+        composer require --dev kriswallsmith/buzz nyholm/psr7
+        sh scripts/tests.sh
+        composer remove --dev kriswallsmith/buzz nyholm/psr7

+ 3 - 0
vendor/meilisearch/meilisearch-php/.gitignore

@@ -0,0 +1,3 @@
+composer.lock
+/vendor/
+.php_cs.cache

+ 19 - 0
vendor/meilisearch/meilisearch-php/.php_cs.dist

@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+
+$finder = \PhpCsFixer\Finder::create()
+    ->in(__DIR__.DIRECTORY_SEPARATOR.'src')
+    ->in(__DIR__.DIRECTORY_SEPARATOR.'tests')
+    ->append(['.php_cs.dist']);
+
+$rules = [
+    '@Symfony' => true,
+    'declare_strict_types' => true,
+    'void_return' => true,
+    'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'namespaced'],
+];
+
+return \PhpCsFixer\Config::create()
+    ->setRules($rules)
+    ->setFinder($finder);

+ 111 - 0
vendor/meilisearch/meilisearch-php/CONTRIBUTING.md

@@ -0,0 +1,111 @@
+# Contributing
+
+First of all, thank you for contributing to MeiliSearch! The goal of this document is to provide everything you need to know in order to contribute to MeiliSearch and its different integrations.
+
+<!-- MarkdownTOC autolink="true" style="ordered" indent="   " -->
+
+- [Assumptions](#assumptions)
+- [How to Contribute](#how-to-contribute)
+- [Development Workflow](#development-workflow)
+- [Git Guidelines](#git-guidelines)
+- [Release Process (for internal team only)](#release-process-for-internal-team-only)
+
+<!-- /MarkdownTOC -->
+
+## Assumptions
+
+1. **You're familiar with [GitHub](https://github.com) and the [Pull Request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) (PR) workflow.**
+2. **You've read the MeiliSearch [documentation](https://docs.meilisearch.com) and the [README](/README.md).**
+3. **You know about the [MeiliSearch community](https://docs.meilisearch.com/learn/what_is_meilisearch/contact.html). Please use this for help.**
+
+## How to Contribute
+
+1. Make sure that the contribution you want to make is explained or detailed in a GitHub issue! Find an [existing issue](https://github.com/meilisearch/meilisearch-php/issues/) or [open a new one](https://github.com/meilisearch/meilisearch-php/issues/new).
+2. Once done, [fork the meilisearch-php repository](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) in your own GitHub account. Ask a maintainer if you want your issue to be checked before making a PR.
+3. [Create a new Git branch](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-and-deleting-branches-within-your-repository).
+4. Review the [Development Workflow](#workflow) section that describes the steps to maintain the repository.
+5. Make the changes on your branch.
+6. [Submit the branch as a PR](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork) pointing to the `main` branch of the main meilisearch-php repository. A maintainer should comment and/or review your Pull Request within a few days. Although depending on the circumstances, it may take longer.<br>
+ We do not enforce a naming convention for the PRs, but **please use something descriptive of your changes**, having in mind that the title of your PR will be automatically added to the next [release changelog](https://github.com/meilisearch/meilisearch-php/releases/).
+
+## Development Workflow
+
+### Setup
+
+```bash
+composer install
+```
+
+### Tests and Linter
+
+Each PR should pass the tests and the linter to be accepted.
+
+```bash
+# Tests
+docker pull getmeili/meilisearch:latest # Fetch the latest version of MeiliSearch image from Docker Hub
+docker run -p 7700:7700 getmeili/meilisearch:latest ./meilisearch --master-key=masterKey --no-analytics=true
+composer test
+# Linter (with auto-fix)
+composer lint:fix
+# Linter (without auto-fix)
+composer lint
+```
+
+## Git Guidelines
+
+### Git Branches
+
+All changes must be made in a branch and submitted as PR.
+We do not enforce any branch naming style, but please use something descriptive of your changes.
+
+### Git Commits
+
+As minimal requirements, your commit message should:
+- be capitalized
+- not finish by a dot or any other punctuation character (!,?)
+- start with a verb so that we can read your commit message this way: "This commit will ...", where "..." is the commit message.
+  e.g.: "Fix the home page button" or "Add more tests for create_index method"
+
+We don't follow any other convention, but if you want to use one, we recommend [this one](https://chris.beams.io/posts/git-commit/).
+
+### GitHub Pull Requests
+
+Some notes on GitHub PRs:
+
+- [Convert your PR as a draft](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/changing-the-stage-of-a-pull-request) if your changes are a work in progress: no one will review it until you pass your PR as ready for review.<br>
+  The draft PR can be very useful if you want to show that you are working on something and make your work visible.
+- The branch related to the PR must be **up-to-date with `main`** before merging. Fortunately, this project [integrates a bot](https://github.com/meilisearch/integration-guides/blob/main/guides/bors.md) to automatically enforce this requirement without the PR author having to do it manually..
+- All PRs must be reviewed and approved by at least one maintainer.
+- The PR title should be accurate and descriptive of the changes. The title of the PR will be indeed automatically added to the next [release changelogs](https://github.com/meilisearch/meilisearch-php/releases/).
+
+## Release Process (for internal team only)
+
+MeiliSearch tools follow the [Semantic Versioning Convention](https://semver.org/).
+
+### Automation to Rebase and Merge the PRs
+
+This project integrates a bot that helps us manage pull requests merging.<br>
+_[Read more about this](https://github.com/meilisearch/integration-guides/blob/main/guides/bors.md)._
+
+### Automated Changelogs
+
+This project integrates a tool to create automated changelogs.<br>
+_[Read more about this](https://github.com/meilisearch/integration-guides/blob/main/guides/release-drafter.md)._
+
+### How to Publish the Release
+
+⚠️ Before doing anything, make sure you got through the guide about [Releasing an Integration](https://github.com/meilisearch/integration-guides/blob/main/guides/integration-release.md).
+
+Make a PR modifying the file [`src/MeiliSearch.php`](/src/MeiliSearch.php) with the right version.
+
+```php
+const VERSION = 'X.X.X';
+```
+
+Once the changes are merged on `main`, you can publish the current draft release via the [GitHub interface](https://github.com/meilisearch/meilisearch-php/releases).
+
+A WebHook will be triggered and push the package to [Packagist](https://packagist.org/packages/meilisearch/meilisearch-php).
+
+<hr>
+
+Thank you again for reading this through, we can not wait to begin to work with you if you made your way through this contributing guide ❤️

+ 21 - 0
vendor/meilisearch/meilisearch-php/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019-2021 Meili
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 232 - 0
vendor/meilisearch/meilisearch-php/README.md

@@ -0,0 +1,232 @@
+<p align="center">
+  <img src="https://res.cloudinary.com/meilisearch/image/upload/v1587402338/SDKs/meilisearch_php.svg" alt="MeiliSearch-PHP" width="200" height="200" />
+</p>
+
+<h1 align="center">MeiliSearch PHP</h1>
+
+<h4 align="center">
+  <a href="https://github.com/meilisearch/MeiliSearch">MeiliSearch</a> |
+  <a href="https://docs.meilisearch.com">Documentation</a> |
+  <a href="https://slack.meilisearch.com">Slack</a> |
+  <a href="https://roadmap.meilisearch.com/tabs/1-under-consideration">Roadmap</a> |
+  <a href="https://www.meilisearch.com">Website</a> |
+  <a href="https://docs.meilisearch.com/faq">FAQ</a>
+</h4>
+
+<p align="center">
+  <a href="https://packagist.org/packages/meilisearch/meilisearch-php"><img src="https://img.shields.io/packagist/v/meilisearch/meilisearch-php" alt="Latest Stable Version"></a>
+  <a href="https://github.com/meilisearch/meilisearch-php/actions"><img src="https://github.com/meilisearch/meilisearch-php/workflows/Tests/badge.svg" alt="Test"></a>
+  <a href="https://github.com/meilisearch/meilisearch-php/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-informational" alt="License"></a>
+  <a href="https://app.bors.tech/repositories/28780"><img src="https://bors.tech/images/badge_small.svg" alt="Bors enabled"></a>
+</p>
+
+<p align="center">⚡ The MeiliSearch API client written for PHP 🐘</p>
+
+**MeiliSearch PHP** is the MeiliSearch API client for PHP developers.
+
+**MeiliSearch** is an open-source search engine. [Discover what MeiliSearch is!](https://github.com/meilisearch/MeiliSearch)
+
+## Table of Contents <!-- omit in toc -->
+
+- [📖 Documentation](#-documentation)
+- [🔧 Installation](#-installation)
+- [🚀 Getting Started](#-getting-started)
+- [🤖 Compatibility with MeiliSearch](#-compatibility-with-meilisearch)
+- [💡 Learn More](#-learn-more)
+- [🧰 HTTP Client Compatibilities](#-http-client-compatibilities)
+  - [Customize your HTTP Client](#customize-your-http-client)
+- [⚙️ Development Workflow and Contributing](#️-development-workflow-and-contributing)
+
+## 📖 Documentation
+
+See our [Documentation](https://docs.meilisearch.com/learn/tutorials/getting_started.html) or our [API References](https://docs.meilisearch.com/reference/api/).
+
+## 🔧 Installation
+
+To get started, simply require the project using [Composer](https://getcomposer.org/).<br>
+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).<br>
+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).
+
+**If you don't know which HTTP client to use, we recommend using Guzzle 7**:
+
+```bash
+composer require meilisearch/meilisearch-php guzzlehttp/guzzle http-interop/http-factory-guzzle:^1.0
+```
+
+Here is an example of installation with the `symfony/http-client`:
+
+```bash
+composer require meilisearch/meilisearch-php symfony/http-client nyholm/psr7:^1.0
+```
+
+💡 *More HTTP client installations compatible with this package can be found [in this section](#-http-client-compatibilities).*
+
+### Run MeiliSearch <!-- omit in toc -->
+
+There are many easy ways to [download and run a MeiliSearch instance](https://docs.meilisearch.com/reference/features/installation.html#download-and-launch).
+
+For example, if you use Docker:
+
+```bash
+docker pull getmeili/meilisearch:latest # Fetch the latest version of MeiliSearch image from Docker Hub
+docker run -it --rm -p 7700:7700 getmeili/meilisearch:latest ./meilisearch --master-key=masterKey
+```
+
+NB: you can also download MeiliSearch from **Homebrew** or **APT**.
+
+## 🚀 Getting Started
+
+#### Add documents <!-- omit in toc -->
+
+```php
+<?php
+
+require_once __DIR__ . '/vendor/autoload.php';
+
+use MeiliSearch\Client;
+
+$client = new Client('http://127.0.0.1:7700', 'masterKey');
+
+# An index is where the documents are stored.
+$index = $client->index('books');
+
+$documents = [
+    ['book_id' => 123,  'title' => 'Pride and Prejudice', 'author' => 'Jane Austen'],
+    ['book_id' => 456,  'title' => 'Le Petit Prince', 'author' => 'Antoine de Saint-Exupéry'],
+    ['book_id' => 1,    'title' => 'Alice In Wonderland', 'author' => 'Lewis Carroll'],
+    ['book_id' => 1344, 'title' => 'The Hobbit', 'author' => 'J. R. R. Tolkien'],
+    ['book_id' => 4,    'title' => 'Harry Potter and the Half-Blood Prince', 'author' => 'J. K. Rowling'],
+    ['book_id' => 42,   'title' => 'The Hitchhiker\'s Guide to the Galaxy', 'author' => 'Douglas Adams, Eoin Colfer, Thomas Tidholm'],
+];
+
+# If the index 'books' does not exist, MeiliSearch creates it when you first add the documents.
+$index->addDocuments($documents); // => { "updateId": 0 }
+```
+
+With the `updateId`, you can check the status (`enqueued`, `processed` or `failed`) of your documents addition using the [update endpoint](https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status).
+
+
+#### Basic Search <!-- omit in toc -->
+
+```php
+// MeiliSearch is typo-tolerant:
+$hits = $index->search('harry pottre')->getHits();
+print_r($hits);
+```
+
+Output:
+
+```php
+Array
+(
+    [0] => Array
+        (
+            [id] => 4
+            [title] => Harry Potter and the Half-Blood Prince
+        )
+)
+```
+
+#### Custom Search <!-- omit in toc -->
+
+All the supported options are described in the [search parameters](https://docs.meilisearch.com/reference/features/search_parameters.html) section of the documentation.
+
+💡 **More about the `search()` method in [the Wiki](https://github.com/meilisearch/meilisearch-php/wiki/Search).**
+
+```php
+$index->search(
+    'prince',
+    [
+        'attributesToHighlight' => ['*'],
+        'filters' => 'book_id > 10'
+    ]
+)->getRaw(); // Return in Array format
+```
+
+JSON output:
+
+```json
+{
+    "hits": [
+        {
+            "book_id": 456,
+            "title": "Le Petit Prince"
+        }
+    ],
+    "offset": 0,
+    "limit": 20,
+    "processingTimeMs": 10,
+    "query": "prince"
+}
+```
+
+## 🤖 Compatibility with MeiliSearch
+
+This package only guarantees the compatibility with the [version v0.20.0 of MeiliSearch](https://github.com/meilisearch/MeiliSearch/releases/tag/v0.20.0).
+
+## 💡 Learn More
+
+The following sections may interest you:
+
+- **Manipulate documents**: see the [API references](https://docs.meilisearch.com/reference/api/documents.html) or read more about [documents](https://docs.meilisearch.com/learn/core_concepts/documents.html).
+- **Search**: see the [API references](https://docs.meilisearch.com/reference/api/search.html) or follow our guide on [search parameters](https://docs.meilisearch.com/reference/features/search_parameters.html).
+- **Manage the indexes**: see the [API references](https://docs.meilisearch.com/reference/api/indexes.html) or read more about [indexes](https://docs.meilisearch.com/learn/core_concepts/indexes.html).
+- **Configure the index settings**: see the [API references](https://docs.meilisearch.com/reference/api/settings.html) or follow our guide on [settings parameters](https://docs.meilisearch.com/reference/features/settings.html).
+
+## 🧰 HTTP Client Compatibilities
+
+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.<br>
+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).
+
+If you want to use this `meilisearch-php`:
+
+- with `guzzlehttp/guzzle` (Guzzle 7), run:
+
+```bash
+composer require meilisearch/meilisearch-php guzzlehttp/guzzle http-interop/http-factory-guzzle:^1.0
+```
+
+- with `php-http/guzzle6-adapter` (Guzzle < 7), run:
+
+```bash
+composer require meilisearch/meilisearch-php php-http/guzzle6-adapter:^2.0 http-interop/http-factory-guzzle:^1.0
+```
+
+- with `symfony/http-client`, run:
+
+```bash
+composer require meilisearch/meilisearch-php symfony/http-client nyholm/psr7:^1.0
+```
+
+- with `php-http/curl-client`, run:
+
+```bash
+composer require meilisearch/meilisearch-php php-http/curl-client nyholm/psr7:^1.0
+```
+
+- with `kriswallsmith/buzz`, run:
+
+```bash
+composer require meilisearch/meilisearch-php kriswallsmith/buzz nyholm/psr7:^1.0
+```
+
+### Customize your HTTP Client
+
+For some reason, you might want to pass a custom configuration to your own HTTP client.<br>
+Make sure you have a [PSR-18](https://www.php-fig.org/psr/psr-18/) compatible client when you initialize the MeiliSearch client.
+
+Following the example in the [Getting Started](#-getting-started) section, with the Guzzle HTTP client:
+
+```php
+new Client('http://127.0.0.1:7700', 'masterKey', new GuzzleHttpClient(['timeout' => 2]));
+```
+
+## ⚙️ Development Workflow and Contributing
+
+Any new contribution is more than welcome in this project!
+
+If you want to know more about the development workflow or want to contribute, please visit our [contributing guidelines](/CONTRIBUTING.md) for detailed instructions!
+
+<hr>
+
+**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.

+ 9 - 0
vendor/meilisearch/meilisearch-php/bors.toml

@@ -0,0 +1,9 @@
+status = [
+    'linter-check',
+    'integration-tests (PHP 7.2)',
+    'integration-tests (PHP 7.3)',
+    'integration-tests (PHP 7.4)',
+    'integration-tests (PHP 8.0)'
+]
+# 1 hour timeout
+timeout-sec = 3600

+ 52 - 0
vendor/meilisearch/meilisearch-php/composer.json

@@ -0,0 +1,52 @@
+{
+    "name": "meilisearch/meilisearch-php",
+    "description": "PHP wrapper for the MeiliSearch API",
+    "keywords": ["meilisearch", "instant", "search", "api", "client", "php"],
+    "type": "library",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Clementine Urquizar",
+            "email": "clementine@meilisearch.com"
+        }
+    ],
+    "minimum-stability": "stable",
+    "require": {
+        "php": ">=7.2",
+        "ext-json": "*",
+        "php-http/discovery": "^1.7",
+        "php-http/httplug": "^2.1",
+        "php-http/client-common": "^2.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "MeiliSearch\\": "src/"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Tests\\": "tests/"
+        }
+    },
+    "suggest": {
+        "guzzlehttp/guzzle": "Use Guzzle ^7 as HTTP client",
+        "http-interop/http-factory-guzzle": "Factory for guzzlehttp/guzzle"
+    },
+    "require-dev": {
+        "phpunit/phpunit": "^8.5 || ^9.0",
+        "friendsofphp/php-cs-fixer": "^2.16",
+        "guzzlehttp/guzzle": "^7.1",
+        "http-interop/http-factory-guzzle": "^1.0"
+    },
+    "scripts": {
+        "lint": [
+            "./vendor/friendsofphp/php-cs-fixer/php-cs-fixer fix --verbose --config=./.php_cs.dist --diff --show-progress=estimating --dry-run --using-cache=yes --allow-risky=yes"
+        ],
+        "lint:fix": [
+            "./vendor/friendsofphp/php-cs-fixer/php-cs-fixer fix --verbose --config=./.php_cs.dist --diff --show-progress=estimating --using-cache=no --allow-risky=yes"
+        ],
+        "test": [
+            "sh scripts/tests.sh"
+        ]
+    }
+}

+ 14 - 0
vendor/meilisearch/meilisearch-php/phpunit.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="vendor/autoload.php"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         stopOnFailure="false"
+         cacheResult="false"
+         colors="true">
+    <testsuites>
+        <testsuite name="Tests">
+            <directory suffix="Test.php">./tests</directory>
+        </testsuite>
+    </testsuites>
+</phpunit>

+ 7 - 0
vendor/meilisearch/meilisearch-php/scripts/tests.sh

@@ -0,0 +1,7 @@
+#!/bin/sh
+
+echo 'Setting FDs limit to 1000'
+ulimit -Sn 1000
+
+echo "Launching tests..."
+vendor/bin/phpunit --color tests/

+ 66 - 0
vendor/meilisearch/meilisearch-php/src/Client.php

@@ -0,0 +1,66 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch;
+
+use MeiliSearch\Delegates\HandlesIndex;
+use MeiliSearch\Delegates\HandlesSystem;
+use MeiliSearch\Endpoints\Delegates\HandlesDumps;
+use MeiliSearch\Endpoints\Dumps;
+use MeiliSearch\Endpoints\Health;
+use MeiliSearch\Endpoints\Indexes;
+use MeiliSearch\Endpoints\Keys;
+use MeiliSearch\Endpoints\Stats;
+use MeiliSearch\Endpoints\Version;
+use Psr\Http\Client\ClientInterface;
+
+class Client
+{
+    use HandlesDumps;
+    use HandlesIndex;
+    use HandlesSystem;
+
+    private $http;
+
+    /**
+     * @var Indexes
+     */
+    private $index;
+
+    /**
+     * @var Health
+     */
+    private $health;
+
+    /**
+     * @var Version
+     */
+    private $version;
+
+    /**
+     * @var Keys
+     */
+    private $keys;
+
+    /**
+     * @var Stats
+     */
+    private $stats;
+
+    /**
+     * @var Dumps
+     */
+    private $dumps;
+
+    public function __construct(string $url, string $apiKey = null, ClientInterface $httpClient = null)
+    {
+        $this->http = new Http\Client($url, $apiKey, $httpClient);
+        $this->index = new Indexes($this->http);
+        $this->health = new Health($this->http);
+        $this->version = new Version($this->http);
+        $this->stats = new Stats($this->http);
+        $this->keys = new Keys($this->http);
+        $this->dumps = new Dumps($this->http);
+    }
+}

+ 23 - 0
vendor/meilisearch/meilisearch-php/src/Contracts/Endpoint.php

@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Contracts;
+
+abstract class Endpoint
+{
+    /**
+     * @var Http
+     */
+    protected $http;
+
+    public function __construct(Http $http)
+    {
+        $this->http = $http;
+    }
+
+    public function show(): ?array
+    {
+        return $this->http->get(static::PATH);
+    }
+}

+ 18 - 0
vendor/meilisearch/meilisearch-php/src/Contracts/Http.php

@@ -0,0 +1,18 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Contracts;
+
+interface Http
+{
+    public function get($path, array $query = []);
+
+    public function post(string $path, $body = null, array $query = []);
+
+    public function put(string $path, $body = null, array $query = []);
+
+    public function patch(string $path, $body = null, array $query = []);
+
+    public function delete($path, array $query = []);
+}

+ 73 - 0
vendor/meilisearch/meilisearch-php/src/Delegates/HandlesIndex.php

@@ -0,0 +1,73 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Delegates;
+
+use MeiliSearch\Endpoints\Indexes;
+use MeiliSearch\Exceptions\ApiException;
+
+/**
+ * @property Indexes index
+ */
+trait HandlesIndex
+{
+    /**
+     * @return Indexes[]
+     */
+    public function getAllIndexes(): array
+    {
+        return $this->index->all();
+    }
+
+    public function index(string $uid): Indexes
+    {
+        return new Indexes($this->http, $uid);
+    }
+
+    public function getIndex(string $uid): Indexes
+    {
+        return $this->index($uid)->fetchInfo();
+    }
+
+    public function deleteIndex(string $uid): array
+    {
+        return $this->index($uid)->delete();
+    }
+
+    public function deleteAllIndexes(): void
+    {
+        $indexes = $this->getAllIndexes();
+        foreach ($indexes as $index) {
+            $index->delete();
+        }
+    }
+
+    public function createIndex(string $uid, array $options = []): Indexes
+    {
+        return $this->index->create($uid, $options);
+    }
+
+    public function updateIndex(string $uid, array $options = []): Indexes
+    {
+        return $this->index($uid)->update($options);
+    }
+
+    /**
+     * @throws ApiException
+     */
+    public function getOrCreateIndex(string $uid, array $options = []): Indexes
+    {
+        try {
+            $index = $this->getIndex($uid, $options);
+        } catch (ApiException $e) {
+            if (\is_array($e->httpBody) && 'index_not_found' === $e->httpBody['errorCode']) {
+                $index = $this->createIndex($uid, $options);
+            } else {
+                throw $e;
+            }
+        }
+
+        return $index;
+    }
+}

+ 52 - 0
vendor/meilisearch/meilisearch-php/src/Delegates/HandlesSystem.php

@@ -0,0 +1,52 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Delegates;
+
+use MeiliSearch\Endpoints\Health;
+use MeiliSearch\Endpoints\Keys;
+use MeiliSearch\Endpoints\Stats;
+use MeiliSearch\Endpoints\SysInfo;
+use MeiliSearch\Endpoints\Version;
+
+/**
+ * @property Health health
+ * @property Version version
+ * @property SysInfo sysInfo
+ * @property Stats stats
+ * @property Keys keys
+ */
+trait HandlesSystem
+{
+    public function health(): ?array
+    {
+        return $this->health->show();
+    }
+
+    public function isHealthy(): bool
+    {
+        try {
+            $this->health->show();
+        } catch (\Exception $e) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public function version(): array
+    {
+        return $this->version->show();
+    }
+
+    public function stats(): array
+    {
+        return $this->stats->show();
+    }
+
+    public function getKeys(): array
+    {
+        return $this->keys->show();
+    }
+}

+ 64 - 0
vendor/meilisearch/meilisearch-php/src/Endpoints/Delegates/HandlesDocuments.php

@@ -0,0 +1,64 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Endpoints\Delegates;
+
+use MeiliSearch\Contracts\Http;
+use MeiliSearch\Exceptions\InvalidArgumentException;
+
+/**
+ * @property Http http
+ */
+trait HandlesDocuments
+{
+    public function getDocument($documentId)
+    {
+        $this->assertValidDocumentId($documentId);
+
+        return $this->http->get(self::PATH.'/'.$this->uid.'/documents/'.$documentId);
+    }
+
+    public function getDocuments(array $query = [])
+    {
+        return $this->http->get(self::PATH.'/'.$this->uid.'/documents', $query);
+    }
+
+    public function addDocuments(array $documents, ?string $primaryKey = null)
+    {
+        return $this->http->post(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey]);
+    }
+
+    public function updateDocuments(array $documents, ?string $primaryKey = null)
+    {
+        return $this->http->put(self::PATH.'/'.$this->uid.'/documents', $documents, ['primaryKey' => $primaryKey]);
+    }
+
+    public function deleteAllDocuments(): array
+    {
+        return $this->http->delete(self::PATH.'/'.$this->uid.'/documents');
+    }
+
+    public function deleteDocument($documentId): array
+    {
+        $this->assertValidDocumentId($documentId);
+
+        return $this->http->delete(self::PATH.'/'.$this->uid.'/documents/'.$documentId);
+    }
+
+    public function deleteDocuments(array $documents): array
+    {
+        return $this->http->post(self::PATH.'/'.$this->uid.'/documents/delete-batch', $documents);
+    }
+
+    private function assertValidDocumentId($documentId): void
+    {
+        if (!\is_string($documentId) && !\is_int($documentId)) {
+            throw InvalidArgumentException::invalidType('documentId', ['string', 'int']);
+        }
+
+        if (\is_string($documentId) && '' === trim($documentId)) {
+            throw InvalidArgumentException::emptyArgument('documentId');
+        }
+    }
+}

+ 23 - 0
vendor/meilisearch/meilisearch-php/src/Endpoints/Delegates/HandlesDumps.php

@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Endpoints\Delegates;
+
+use MeiliSearch\Endpoints\Dumps;
+
+/**
+ * @property Dumps dumps
+ */
+trait HandlesDumps
+{
+    public function createDump(): array
+    {
+        return $this->dumps->create();
+    }
+
+    public function getDumpStatus(string $uid): array
+    {
+        return $this->dumps->status($uid);
+    }
+}

+ 132 - 0
vendor/meilisearch/meilisearch-php/src/Endpoints/Delegates/HandlesSettings.php

@@ -0,0 +1,132 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Endpoints\Delegates;
+
+use MeiliSearch\Contracts\Http;
+
+/**
+ * @property Http http
+ */
+trait HandlesSettings
+{
+    // Settings - Ranking rules
+
+    public function getRankingRules(): array
+    {
+        return $this->http->get(self::PATH.'/'.$this->uid.'/settings/ranking-rules');
+    }
+
+    public function updateRankingRules(array $rankingRules): array
+    {
+        return $this->http->post(self::PATH.'/'.$this->uid.'/settings/ranking-rules', $rankingRules);
+    }
+
+    public function resetRankingRules(): array
+    {
+        return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/ranking-rules');
+    }
+
+    // Settings - Distinct attribute
+
+    public function getDistinctAttribute()
+    {
+        return $this->http->get(self::PATH.'/'.$this->uid.'/settings/distinct-attribute');
+    }
+
+    public function updateDistinctAttribute(string $distinctAttribute): array
+    {
+        return $this->http->post(self::PATH.'/'.$this->uid.'/settings/distinct-attribute', $distinctAttribute);
+    }
+
+    public function resetDistinctAttribute(): array
+    {
+        return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/distinct-attribute');
+    }
+
+    // Settings - Searchable attributes
+
+    public function getSearchableAttributes(): array
+    {
+        return $this->http->get(self::PATH.'/'.$this->uid.'/settings/searchable-attributes');
+    }
+
+    public function updateSearchableAttributes(array $searchableAttributes): array
+    {
+        return $this->http->post(self::PATH.'/'.$this->uid.'/settings/searchable-attributes', $searchableAttributes);
+    }
+
+    public function resetSearchableAttributes(): array
+    {
+        return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/searchable-attributes');
+    }
+
+    // Settings - Displayed attributes
+
+    public function getDisplayedAttributes(): array
+    {
+        return $this->http->get(self::PATH.'/'.$this->uid.'/settings/displayed-attributes');
+    }
+
+    public function updateDisplayedAttributes(array $displayedAttributes): array
+    {
+        return $this->http->post(self::PATH.'/'.$this->uid.'/settings/displayed-attributes', $displayedAttributes);
+    }
+
+    public function resetDisplayedAttributes(): array
+    {
+        return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/displayed-attributes');
+    }
+
+    // Settings - Stop-words
+
+    public function getStopWords(): array
+    {
+        return $this->http->get(self::PATH.'/'.$this->uid.'/settings/stop-words');
+    }
+
+    public function updateStopWords(array $stopWords): array
+    {
+        return $this->http->post(self::PATH.'/'.$this->uid.'/settings/stop-words', $stopWords);
+    }
+
+    public function resetStopWords(): array
+    {
+        return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/stop-words');
+    }
+
+    // Settings - Synonyms
+
+    public function getSynonyms(): array
+    {
+        return $this->http->get(self::PATH.'/'.$this->uid.'/settings/synonyms');
+    }
+
+    public function updateSynonyms(array $synonyms): array
+    {
+        return $this->http->post(self::PATH.'/'.$this->uid.'/settings/synonyms', $synonyms);
+    }
+
+    public function resetSynonyms(): array
+    {
+        return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/synonyms');
+    }
+
+    // Settings - Attributes for faceting
+
+    public function getAttributesForFaceting(): array
+    {
+        return $this->http->get(self::PATH.'/'.$this->uid.'/settings/attributes-for-faceting');
+    }
+
+    public function updateAttributesForFaceting(array $attributesForFaceting): array
+    {
+        return $this->http->post(self::PATH.'/'.$this->uid.'/settings/attributes-for-faceting', $attributesForFaceting);
+    }
+
+    public function resetAttributesForFaceting(): array
+    {
+        return $this->http->delete(self::PATH.'/'.$this->uid.'/settings/attributes-for-faceting');
+    }
+}

+ 22 - 0
vendor/meilisearch/meilisearch-php/src/Endpoints/Dumps.php

@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Endpoints;
+
+use MeiliSearch\Contracts\Endpoint;
+
+class Dumps extends Endpoint
+{
+    protected const PATH = '/dumps';
+
+    public function create(): array
+    {
+        return $this->http->post(self::PATH);
+    }
+
+    public function status(string $uid): array
+    {
+        return $this->http->get(self::PATH.'/'.$uid.'/status');
+    }
+}

+ 12 - 0
vendor/meilisearch/meilisearch-php/src/Endpoints/Health.php

@@ -0,0 +1,12 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Endpoints;
+
+use MeiliSearch\Contracts\Endpoint;
+
+class Health extends Endpoint
+{
+    protected const PATH = '/health';
+}

+ 198 - 0
vendor/meilisearch/meilisearch-php/src/Endpoints/Indexes.php

@@ -0,0 +1,198 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Endpoints;
+
+use Exception;
+use MeiliSearch\Contracts\Endpoint;
+use MeiliSearch\Contracts\Http;
+use MeiliSearch\Endpoints\Delegates\HandlesDocuments;
+use MeiliSearch\Endpoints\Delegates\HandlesSettings;
+use MeiliSearch\Exceptions\ApiException;
+use MeiliSearch\Exceptions\TimeOutException;
+use MeiliSearch\Search\SearchResult;
+
+class Indexes extends Endpoint
+{
+    use HandlesDocuments;
+    use HandlesSettings;
+
+    protected const PATH = '/indexes';
+
+    /**
+     * @var string|null
+     */
+    private $uid;
+    private $primaryKey;
+
+    public function __construct(Http $http, $uid = null, $primaryKey = null)
+    {
+        $this->uid = $uid;
+        $this->primaryKey = $primaryKey;
+        parent::__construct($http);
+    }
+
+    /**
+     * @return $this
+     *
+     * @throws Exception|ApiException
+     */
+    public function create(string $uid, array $options = []): self
+    {
+        $options['uid'] = $uid;
+
+        $response = $this->http->post(self::PATH, $options);
+
+        return new self($this->http, $response['uid'], $response['primaryKey']);
+    }
+
+    public function all(): array
+    {
+        $indexes = [];
+
+        foreach ($this->http->get(self::PATH) as $index) {
+            $indexes[] = new self($this->http, $index['uid']);
+        }
+
+        return $indexes;
+    }
+
+    public function getPrimaryKey(): ?string
+    {
+        return $this->primaryKey;
+    }
+
+    public function fetchPrimaryKey(): ?string
+    {
+        return $this->fetchInfo()->getPrimaryKey();
+    }
+
+    public function getUid(): ?string
+    {
+        return $this->uid;
+    }
+
+    public function fetchRawInfo(): ?array
+    {
+        return $this->http->get(self::PATH.'/'.$this->uid);
+    }
+
+    public function fetchInfo(): self
+    {
+        $response = $this->fetchRawInfo();
+        $this->uid = $response['uid'];
+        $this->primaryKey = $response['primaryKey'];
+
+        return $this;
+    }
+
+    public function update($body): self
+    {
+        $response = $this->http->put(self::PATH.'/'.$this->uid, $body);
+        $this->uid = $response['uid'];
+        $this->primaryKey = $response['primaryKey'];
+
+        return $this;
+    }
+
+    public function delete(): array
+    {
+        return $this->http->delete(self::PATH.'/'.$this->uid) ?? [];
+    }
+
+    // Updates
+
+    public function getUpdateStatus($updateId): array
+    {
+        return $this->http->get(self::PATH.'/'.$this->uid.'/updates/'.$updateId);
+    }
+
+    public function getAllUpdateStatus(): array
+    {
+        return $this->http->get(self::PATH.'/'.$this->uid.'/updates');
+    }
+
+    /**
+     * @param $updateId
+     * @param int $timeoutInMs
+     * @param int $intervalInMs
+     *
+     * @return mixed
+     *
+     * @throws TimeOutException
+     */
+    public function waitForPendingUpdate($updateId, $timeoutInMs = 5000, $intervalInMs = 50): array
+    {
+        $timeout_temp = 0;
+        while ($timeoutInMs > $timeout_temp) {
+            $res = $this->getUpdateStatus($updateId);
+            if ('enqueued' != $res['status']) {
+                return $res;
+            }
+            $timeout_temp += $intervalInMs;
+            usleep(1000 * $intervalInMs);
+        }
+        throw new TimeOutException();
+    }
+
+    // Search
+
+    /**
+     * @param string $query
+     *
+     * @return SearchResult|array
+     */
+    public function search($query, array $searchParams = [], array $options = [])
+    {
+        $result = $this->rawSearch($query, $searchParams);
+
+        if (\array_key_exists('raw', $options) && $options['raw']) {
+            return $result;
+        }
+
+        $searchResult = new SearchResult($result);
+        $searchResult->applyOptions($options);
+
+        return $searchResult;
+    }
+
+    /**
+     * @param string $query
+     *
+     * @return array
+     */
+    public function rawSearch($query, array $searchParams = [])
+    {
+        $parameters = array_merge(
+            ['q' => $query],
+            $searchParams
+        );
+
+        return $this->http->post(self::PATH.'/'.$this->uid.'/search', $parameters);
+    }
+
+    // Stats
+
+    public function stats(): array
+    {
+        return $this->http->get(self::PATH.'/'.$this->uid.'/stats');
+    }
+
+    // Settings - Global
+
+    public function getSettings(): array
+    {
+        return $this->http->get(self::PATH.'/'.$this->uid.'/settings');
+    }
+
+    public function updateSettings($settings): array
+    {
+        return $this->http->post(self::PATH.'/'.$this->uid.'/settings', $settings);
+    }
+
+    public function resetSettings(): array
+    {
+        return $this->http->delete(self::PATH.'/'.$this->uid.'/settings');
+    }
+}

+ 12 - 0
vendor/meilisearch/meilisearch-php/src/Endpoints/Keys.php

@@ -0,0 +1,12 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Endpoints;
+
+use MeiliSearch\Contracts\Endpoint;
+
+class Keys extends Endpoint
+{
+    protected const PATH = '/keys';
+}

+ 12 - 0
vendor/meilisearch/meilisearch-php/src/Endpoints/Stats.php

@@ -0,0 +1,12 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Endpoints;
+
+use MeiliSearch\Contracts\Endpoint;
+
+class Stats extends Endpoint
+{
+    protected const PATH = '/stats';
+}

+ 12 - 0
vendor/meilisearch/meilisearch-php/src/Endpoints/Version.php

@@ -0,0 +1,12 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Endpoints;
+
+use MeiliSearch\Contracts\Endpoint;
+
+class Version extends Endpoint
+{
+    protected const PATH = '/version';
+}

+ 88 - 0
vendor/meilisearch/meilisearch-php/src/Exceptions/ApiException.php

@@ -0,0 +1,88 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Exceptions;
+
+use Exception;
+
+class ApiException extends Exception
+{
+    public $httpStatus = 0;
+    public $message = null;
+    public $errorCode = null;
+    public $errorType = null;
+    public $errorLink = null;
+    public $httpBody = null;
+
+    public function __construct($httpStatus, $httpBody, $previous = null)
+    {
+        $this->httpBody = $httpBody;
+        $this->httpStatus = $httpStatus;
+        $this->message = $this->getMessageFromHttpBody();
+        $this->errorCode = $this->getErrorCodeFromHttpBody();
+        $this->errorLink = $this->getErrorLinkFromHttpBody();
+        $this->errorType = $this->getErrorTypeFromHttpBody();
+
+        parent::__construct($this->message, $this->httpStatus, $previous);
+    }
+
+    public function __toString()
+    {
+        $base = 'MeiliSearch HTTPRequestException: Http Status: '.$this->httpStatus;
+
+        if ($this->message) {
+            $base .= ' - Message: '.$this->message;
+        }
+
+        if ($this->errorCode) {
+            $base .= ' - Error code: '.$this->errorCode;
+        }
+
+        if ($this->errorType) {
+            $base .= ' - Error type: '.$this->errorType;
+        }
+
+        if ($this->errorLink) {
+            $base .= ' - Error link: '.$this->errorLink;
+        }
+
+        return $base;
+    }
+
+    private function getMessageFromHttpBody(): ?string
+    {
+        if (\is_array($this->httpBody) && \array_key_exists('message', $this->httpBody)) {
+            return $this->httpBody['message'];
+        }
+
+        return null;
+    }
+
+    private function getErrorCodeFromHttpBody(): ?string
+    {
+        if (\is_array($this->httpBody) && \array_key_exists('errorCode', $this->httpBody)) {
+            return $this->httpBody['errorCode'];
+        }
+
+        return null;
+    }
+
+    private function getErrorTypeFromHttpBody(): ?string
+    {
+        if (\is_array($this->httpBody) && \array_key_exists('errorType', $this->httpBody)) {
+            return $this->httpBody['errorType'];
+        }
+
+        return null;
+    }
+
+    private function getErrorLinkFromHttpBody(): ?string
+    {
+        if (\is_array($this->httpBody) && \array_key_exists('errorLink', $this->httpBody)) {
+            return $this->httpBody['errorLink'];
+        }
+
+        return null;
+    }
+}

+ 15 - 0
vendor/meilisearch/meilisearch-php/src/Exceptions/CommunicationException.php

@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Exceptions;
+
+use Exception;
+
+class CommunicationException extends Exception
+{
+    public function __toString()
+    {
+        return 'MeiliSearch CommunicationException: '.$this->getMessage();
+    }
+}

+ 28 - 0
vendor/meilisearch/meilisearch-php/src/Exceptions/InvalidArgumentException.php

@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Exceptions;
+
+use Exception;
+
+final class InvalidArgumentException extends Exception
+{
+    public static function invalidType(string $argumentName, array $validTypes): self
+    {
+        return new self(
+            sprintf('Argument "%s" is not a valid type! Please provide an argument that is of type: "%s"', $argumentName, \implode('","', $validTypes)),
+            400,
+            null
+        );
+    }
+
+    public static function emptyArgument(string $argumentName): self
+    {
+        return new self(
+            sprintf('Argument "%s" is empty.', $argumentName),
+            400,
+            null
+        );
+    }
+}

+ 32 - 0
vendor/meilisearch/meilisearch-php/src/Exceptions/TimeOutException.php

@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Exceptions;
+
+class TimeOutException extends \Exception
+{
+    public $code = 408;
+    public $message = 'Request timed out';
+
+    public function __construct($message = null, $code = null, $previous = null)
+    {
+        if (isset($message)) {
+            $this->message = $message;
+        }
+        if (isset($code)) {
+            $this->code = $code;
+        }
+        parent::__construct($this->message, $this->code, $previous);
+    }
+
+    public function __toString()
+    {
+        $base = 'MeiliSearch TimeOutException: Code: '.$this->code;
+        if (isset($this->message)) {
+            return $base.' - Message: '.$this->message;
+        } else {
+            return $base;
+        }
+    }
+}

+ 195 - 0
vendor/meilisearch/meilisearch-php/src/Http/Client.php

@@ -0,0 +1,195 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Http;
+
+use Http\Discovery\Psr17FactoryDiscovery;
+use Http\Discovery\Psr18ClientDiscovery;
+use MeiliSearch\Contracts\Http;
+use MeiliSearch\Exceptions\ApiException;
+use MeiliSearch\Exceptions\CommunicationException;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Client\ClientInterface;
+use Psr\Http\Client\NetworkExceptionInterface;
+use Psr\Http\Message\RequestFactoryInterface;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\StreamFactoryInterface;
+
+class Client implements Http
+{
+    /**
+     * @var Http
+     */
+    private $http;
+
+    /**
+     * @var RequestFactoryInterface
+     */
+    private $requestFactory;
+
+    /**
+     * @var StreamFactoryInterface
+     */
+    private $streamFactory;
+
+    /**
+     * @var array
+     */
+    private $headers;
+
+    /**
+     * @var string
+     */
+    private $apiKey;
+
+    private $baseUrl;
+
+    /**
+     * Client constructor.
+     *
+     * @param string $apiKey
+     */
+    public function __construct(string $url, string $apiKey = null, ClientInterface $httpClient = null)
+    {
+        $this->baseUrl = $url;
+        $this->apiKey = $apiKey;
+        $this->http = $httpClient ?? Psr18ClientDiscovery::find();
+        $this->requestFactory = Psr17FactoryDiscovery::findRequestFactory();
+        $this->streamFactory = Psr17FactoryDiscovery::findStreamFactory();
+        $this->headers = array_filter([
+            'Content-type' => 'application/json',
+            'X-Meili-API-Key' => $this->apiKey,
+        ]);
+    }
+
+    /**
+     * @param $path
+     * @param array $query
+     *
+     * @return mixed
+     *
+     * @throws ClientExceptionInterface
+     * @throws ApiException
+     */
+    public function get($path, $query = [])
+    {
+        $request = $this->requestFactory->createRequest(
+            'GET',
+            $this->baseUrl.$path.$this->buildQueryString($query)
+        );
+
+        return $this->execute($request);
+    }
+
+    /**
+     * @param string $path
+     * @param null   $body
+     * @param array  $query
+     *
+     * @return mixed
+     *
+     * @throws ClientExceptionInterface
+     * @throws ApiException
+     */
+    public function post($path, $body = null, $query = [])
+    {
+        $request = $this->requestFactory->createRequest(
+            'POST',
+            $this->baseUrl.$path.$this->buildQueryString($query)
+        )->withBody($this->streamFactory->createStream(json_encode($body)));
+
+        return $this->execute($request);
+    }
+
+    public function put($path, $body = null, $query = [])
+    {
+        $request = $this->requestFactory->createRequest(
+            'PUT',
+            $this->baseUrl.$path.$this->buildQueryString($query)
+        )->withBody($this->streamFactory->createStream(json_encode($body)));
+
+        return $this->execute($request);
+    }
+
+    /**
+     * @param string $path
+     * @param null   $body
+     * @param array  $query
+     *
+     * @return mixed
+     *
+     * @throws ClientExceptionInterface
+     * @throws ApiException
+     */
+    public function patch($path, $body = null, $query = [])
+    {
+        $request = $this->requestFactory->createRequest(
+            'PATCH',
+            $this->baseUrl.$path.$this->buildQueryString($query)
+        )->withBody($this->streamFactory->createStream(json_encode($body)));
+
+        return $this->execute($request);
+    }
+
+    /**
+     * @param $path
+     * @param array $query
+     *
+     * @return mixed
+     *
+     * @throws ClientExceptionInterface
+     * @throws ApiException
+     */
+    public function delete($path, $query = [])
+    {
+        $request = $this->requestFactory->createRequest(
+            'DELETE',
+            $this->baseUrl.$path.$this->buildQueryString($query)
+        );
+
+        return $this->execute($request);
+    }
+
+    /**
+     * @return mixed
+     *
+     * @throws ApiException
+     * @throws ClientExceptionInterface
+     * @throws CommunicationException
+     * @throws ApiException
+     */
+    private function execute(RequestInterface $request)
+    {
+        foreach ($this->headers as $header => $value) {
+            $request = $request->withAddedHeader($header, $value);
+        }
+
+        try {
+            return $this->parseResponse($this->http->sendRequest($request));
+        } catch (NetworkExceptionInterface $e) {
+            throw new CommunicationException($e->getMessage(), $e->getCode(), $e);
+        }
+    }
+
+    private function buildQueryString(array $queryParams = []): string
+    {
+        return $queryParams ? '?'.http_build_query($queryParams) : '';
+    }
+
+    /**
+     * @return mixed
+     *
+     * @throws ApiException
+     */
+    private function parseResponse(ResponseInterface $response)
+    {
+        if ($response->getStatusCode() >= 300) {
+            $body = json_decode($response->getBody()->getContents(), true) ?? $response->getReasonPhrase();
+            throw new ApiException($response->getStatusCode(), $body);
+        }
+
+        return json_decode($response->getBody()->getContents(), true);
+    }
+}

+ 10 - 0
vendor/meilisearch/meilisearch-php/src/MeiliSearch.php

@@ -0,0 +1,10 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch;
+
+class MeiliSearch
+{
+    const VERSION = '0.17.2';
+}

+ 248 - 0
vendor/meilisearch/meilisearch-php/src/Search/SearchResult.php

@@ -0,0 +1,248 @@
+<?php
+
+declare(strict_types=1);
+
+namespace MeiliSearch\Search;
+
+use function array_filter;
+use ArrayIterator;
+use Countable;
+use IteratorAggregate;
+
+class SearchResult implements Countable, IteratorAggregate
+{
+    /**
+     * @var array<int, array<string, mixed>>
+     */
+    private $hits;
+
+    /**
+     * @var int
+     */
+    private $offset;
+
+    /**
+     * @var int
+     */
+    private $limit;
+
+    /**
+     * `nbHits` is the attributes returned by the MeiliSearch server
+     * and its value will not be modified by the methods in this class.
+     * Please, use `hitsCount` if you want to know the real size of the `hits` array at any time.
+     *
+     * @var int
+     */
+    private $nbHits;
+
+    /**
+     * @var int
+     */
+    private $hitsCount;
+
+    /**
+     * @var bool
+     */
+    private $exhaustiveNbHits;
+
+    /**
+     * @var int
+     */
+    private $processingTimeMs;
+
+    /**
+     * @var string
+     */
+    private $query;
+
+    /**
+     * @var bool|null
+     */
+    private $exhaustiveFacetsCount;
+
+    /**
+     * @var array<string, mixed>
+     */
+    private $facetsDistribution;
+
+    /**
+     * @var array<string, mixed>
+     */
+    private $raw;
+
+    public function __construct(array $body)
+    {
+        $this->hits = $body['hits'] ?? [];
+        $this->offset = $body['offset'];
+        $this->limit = $body['limit'];
+        $this->nbHits = $body['nbHits'];
+        $this->hitsCount = \count($body['hits']);
+        $this->exhaustiveNbHits = $body['exhaustiveNbHits'] ?? false;
+        $this->processingTimeMs = $body['processingTimeMs'];
+        $this->query = $body['query'];
+        $this->exhaustiveFacetsCount = $body['exhaustiveFacetsCount'] ?? null;
+        $this->facetsDistribution = $body['facetsDistribution'] ?? [];
+        $this->raw = $body;
+    }
+
+    /**
+     * Return a new {@see SearchResult} instance.
+     *
+     * The $options parameter is an array, and the following keys are accepted:
+     * - removeZeroFacets (boolean)
+     * - transformFacetsDistribution (callable)
+     * - transformHits (callable)
+     *
+     * The method does NOT trigger a new search.
+     *
+     * @return SearchResult
+     */
+    public function applyOptions($options): self
+    {
+        if (\array_key_exists('removeZeroFacets', $options) && true === $options['removeZeroFacets']) {
+            $this->removeZeroFacets();
+        }
+        if (\array_key_exists('transformHits', $options) && \is_callable($options['transformHits'])) {
+            $this->transformHits($options['transformHits']);
+        }
+        if (\array_key_exists('transformFacetsDistribution', $options) && \is_callable($options['transformFacetsDistribution'])) {
+            $this->transformFacetsDistribution($options['transformFacetsDistribution']);
+        }
+
+        return $this;
+    }
+
+    public function transformHits(callable $callback): self
+    {
+        $this->hits = $callback($this->hits);
+        $this->hitsCount = \count($this->hits);
+
+        return $this;
+    }
+
+    public function transformFacetsDistribution(callable $callback): self
+    {
+        $this->facetsDistribution = $callback($this->facetsDistribution);
+
+        return $this;
+    }
+
+    public function removeZeroFacets(): self
+    {
+        $filterAllFacets = function (array $facets) {
+            $filterOneFacet = function (array $facet) {
+                return array_filter(
+                    $facet,
+                    function ($v, $k) { return 0 !== $v; },
+                    ARRAY_FILTER_USE_BOTH
+                );
+            };
+
+            return array_map($filterOneFacet, $facets);
+        };
+
+        return $this->transformFacetsDistribution($filterAllFacets);
+    }
+
+    public function getHit(int $key, $default = null)
+    {
+        return $this->hits[$key] ?? $default;
+    }
+
+    /**
+     * @return array<int, array>
+     */
+    public function getHits(): array
+    {
+        return $this->hits;
+    }
+
+    public function getOffset(): int
+    {
+        return $this->offset;
+    }
+
+    public function getLimit(): int
+    {
+        return $this->limit;
+    }
+
+    public function getHitsCount(): int
+    {
+        return $this->hitsCount;
+    }
+
+    public function count(): int
+    {
+        return $this->hitsCount;
+    }
+
+    public function getNbHits(): int
+    {
+        return $this->nbHits;
+    }
+
+    public function getExhaustiveNbHits(): bool
+    {
+        return $this->exhaustiveNbHits;
+    }
+
+    public function getProcessingTimeMs(): int
+    {
+        return $this->processingTimeMs;
+    }
+
+    public function getQuery(): string
+    {
+        return $this->query;
+    }
+
+    public function getExhaustiveFacetsCount(): ?bool
+    {
+        return $this->exhaustiveFacetsCount;
+    }
+
+    /**
+     * @return array<string, mixed>
+     */
+    public function getFacetsDistribution(): array
+    {
+        return $this->facetsDistribution;
+    }
+
+    /**
+     * Return the original search result.
+     *
+     * @return array<string, mixed>
+     */
+    public function getRaw(): array
+    {
+        return $this->raw;
+    }
+
+    public function toArray(): array
+    {
+        return [
+            'hits' => $this->hits,
+            'offset' => $this->offset,
+            'limit' => $this->limit,
+            'nbHits' => $this->nbHits,
+            'hitsCount' => $this->hitsCount,
+            'exhaustiveNbHits' => $this->exhaustiveNbHits,
+            'processingTimeMs' => $this->processingTimeMs,
+            'query' => $this->query,
+            'exhaustiveFacetsCount' => $this->exhaustiveFacetsCount,
+            'facetsDistribution' => $this->facetsDistribution,
+        ];
+    }
+
+    public function toJSON(): string
+    {
+        return \json_encode($this->toArray(), JSON_PRETTY_PRINT);
+    }
+
+    public function getIterator(): ArrayIterator
+    {
+        return new ArrayIterator($this->hits);
+    }
+}

+ 310 - 0
vendor/meilisearch/meilisearch-php/tests/Endpoints/ClientTest.php

@@ -0,0 +1,310 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Endpoints;
+
+use MeiliSearch\Client;
+use MeiliSearch\Endpoints\Indexes;
+use MeiliSearch\Exceptions\ApiException;
+use Tests\TestCase;
+
+final class ClientTest extends TestCase
+{
+    public function testGetAllIndexesWhenEmpty(): void
+    {
+        $response = $this->client->getAllIndexes();
+
+        $this->assertIsArray($response);
+        $this->assertEmpty($response);
+    }
+
+    public function testCreateIndexWithOnlyUid(): void
+    {
+        $index = $this->client->createIndex('index');
+
+        $this->assertInstanceOf(Indexes::class, $index);
+        $this->assertSame('index', $index->getUid());
+        $this->assertNull($index->getPrimaryKey());
+    }
+
+    public function testCreateIndexWithUidAndPrimaryKey(): void
+    {
+        $index = $this->client->createIndex(
+            'index',
+            ['primaryKey' => 'ObjectId']
+        );
+
+        $this->assertInstanceOf(Indexes::class, $index);
+        $this->assertSame('index', $index->getUid());
+        $this->assertSame('ObjectId', $index->getPrimaryKey());
+    }
+
+    public function testCreateIndexWithUidInOptions(): void
+    {
+        $index = $this->client->createIndex(
+            'index',
+            [
+                'uid' => 'wrong',
+                'primaryKey' => 'ObjectId',
+            ]
+        );
+
+        $this->assertInstanceOf(Indexes::class, $index);
+        $this->assertSame('index', $index->getUid());
+        $this->assertSame('ObjectId', $index->getPrimaryKey());
+    }
+
+    public function testGetAllIndexes(): void
+    {
+        $indexA = 'indexA';
+        $indexB = 'indexB';
+        $this->client->createIndex($indexA);
+        $this->client->createIndex($indexB);
+
+        $response = $this->client->getAllIndexes();
+
+        $this->assertIsArray($response);
+        $this->assertCount(2, $response);
+
+        $uids = array_map(function ($index) {
+            return $index->getUid();
+        }, $response);
+
+        $this->assertContains($indexA, $uids);
+        $this->assertContains($indexB, $uids);
+    }
+
+    public function testUpdateIndex(): void
+    {
+        $this->client->createIndex('indexA');
+
+        $index = $this->client->updateIndex('indexA', ['primaryKey' => 'id']);
+
+        $this->assertInstanceOf(Indexes::class, $index);
+        $this->assertSame($index->getPrimaryKey(), 'id');
+        $this->assertSame($index->getUid(), 'indexA');
+    }
+
+    public function testDeleteIndex(): void
+    {
+        $this->client->createIndex('index');
+
+        $response = $this->client->getAllIndexes();
+        $this->assertCount(1, $response);
+
+        $response = $this->client->deleteIndex('index');
+
+        $this->assertEmpty($response);
+        $response = $this->client->getAllIndexes();
+
+        $this->assertCount(0, $response);
+    }
+
+    public function testDeleteAllIndexes(): void
+    {
+        $this->client->createIndex('index-1');
+        $this->client->createIndex('index-2');
+
+        $response = $this->client->getAllIndexes();
+
+        $this->assertCount(2, $response);
+
+        $this->client->deleteAllIndexes();
+        $response = $this->client->getAllIndexes();
+
+        $this->assertCount(0, $response);
+    }
+
+    public function testDeleteAllIndexesWhenThereAreNoIndexes(): void
+    {
+        $response = $this->client->getAllIndexes();
+        $this->assertCount(0, $response);
+
+        $this->client->deleteAllIndexes();
+
+        $this->assertCount(0, $response);
+    }
+
+    public function testGetIndex(): void
+    {
+        $this->client->createIndex('index');
+
+        $index = $this->client->getIndex('index');
+        $this->assertInstanceOf(Indexes::class, $index);
+        $this->assertSame('index', $index->getUid());
+        $this->assertNull($index->getPrimaryKey());
+    }
+
+    public function testIndex(): void
+    {
+        $this->client->createIndex('index');
+
+        $index = $this->client->index('index');
+        $this->assertInstanceOf(Indexes::class, $index);
+        $this->assertSame('index', $index->getUid());
+        $this->assertNull($index->getPrimaryKey());
+    }
+
+    public function testGetOrCreateIndexWithOnlyUid(): void
+    {
+        $index = $this->client->getOrCreateIndex('index');
+
+        $this->assertInstanceOf(Indexes::class, $index);
+        $this->assertSame('index', $index->getUid());
+        $this->assertNull($index->getPrimaryKey());
+    }
+
+    public function testGetOrCreateIndexWithUidAndPrimaryKey(): void
+    {
+        $index = $this->client->getOrCreateIndex(
+            'index',
+            ['primaryKey' => 'ObjectId']
+        );
+
+        $this->assertInstanceOf(Indexes::class, $index);
+        $this->assertSame('index', $index->getUid());
+        $this->assertSame('ObjectId', $index->getPrimaryKey());
+    }
+
+    public function testGetOrCreateIndexWithUidInOptions(): void
+    {
+        $index = $this->client->getOrCreateIndex(
+            'index',
+            [
+                'uid' => 'wrong',
+                'primaryKey' => 'ObjectId',
+            ]
+        );
+
+        $this->assertInstanceOf(Indexes::class, $index);
+        $this->assertSame('index', $index->getUid());
+        $this->assertSame('ObjectId', $index->getPrimaryKey());
+    }
+
+    public function testGetOrCreateWithIndexAlreadyExists(): void
+    {
+        $index1 = $this->client->getOrCreateIndex('index');
+        $index2 = $this->client->getOrCreateIndex('index');
+        $index3 = $this->client->getOrCreateIndex('index');
+
+        $this->assertSame('index', $index1->getUid());
+        $this->assertSame('index', $index2->getUid());
+        $this->assertSame('index', $index3->getUid());
+
+        $update = $index1->addDocuments([['book_id' => 1, 'name' => 'Some book']]);
+        $index1->waitForPendingUpdate($update['updateId']);
+
+        $documents = $index2->getDocuments();
+        $this->assertCount(1, $documents);
+        $index2->delete();
+    }
+
+    public function testExceptionIsThrownWhenOverwritingPrimaryKeyUsingUpdateIndex(): void
+    {
+        $this->client->createIndex(
+            'indexB',
+            ['primaryKey' => 'objectId']
+        );
+
+        $this->expectException(ApiException::class);
+
+        $this->client->updateIndex('indexB', ['primaryKey' => 'objectID']);
+    }
+
+    public function testExceptionIsThrownWhenUpdateIndexUseANoneExistingIndex(): void
+    {
+        $this->expectException(ApiException::class);
+
+        $this->client->updateIndex(
+            'IndexNotExist',
+            ['primaryKey' => 'objectId']
+        );
+    }
+
+    public function testExceptionIfUidTakenWhenCreating(): void
+    {
+        $this->client->createIndex('index');
+
+        $this->expectException(ApiException::class);
+
+        $this->client->createIndex('index');
+    }
+
+    public function testExceptionIfUidIsNullWhenCreating(): void
+    {
+        $this->expectException(\TypeError::class);
+        $this->client->createIndex(null);
+    }
+
+    public function testExceptionIfUidIsEmptyStringWhenCreating(): void
+    {
+        $this->expectException(ApiException::class);
+        $this->client->createIndex('');
+    }
+
+    public function testExceptionIfNoIndexWhenShowing(): void
+    {
+        $this->expectException(ApiException::class);
+        $this->client->getIndex('a-non-existing-index');
+    }
+
+    public function testExceptionIfNoIndexWhenDeleting(): void
+    {
+        $this->expectException(ApiException::class);
+        $this->client->deleteIndex('a-non-existing-index');
+    }
+
+    public function testHealth(): void
+    {
+        $response = $this->client->health();
+
+        $this->assertEquals('available', $response['status']);
+    }
+
+    public function testIsHealthyIsTrue(): void
+    {
+        $response = $this->client->isHealthy();
+
+        $this->assertTrue($response);
+    }
+
+    public function testIsHealthyIsFalse(): void
+    {
+        $client = new Client('http://127.0.0.1.com:1234', 'masterKey');
+        $response = $client->isHealthy();
+
+        $this->assertFalse($response);
+    }
+
+    public function testVersion(): void
+    {
+        $response = $this->client->version();
+
+        $this->assertArrayHasKey('commitSha', $response);
+        $this->assertArrayHasKey('buildDate', $response);
+        $this->assertArrayHasKey('pkgVersion', $response);
+    }
+
+    public function testStats(): void
+    {
+        $response = $this->client->stats();
+
+        $this->assertArrayHasKey('databaseSize', $response);
+        $this->assertArrayHasKey('lastUpdate', $response);
+        $this->assertArrayHasKey('indexes', $response);
+    }
+
+    public function testBadClientUrl(): void
+    {
+        try {
+            $client = new Client('http://127.0.0.1.com:1234', 'some-key');
+            $client->createIndex('index');
+        } catch (\Exception $e) {
+            $this->assertIsString($e->getMessage());
+
+            return;
+        }
+        $this->fail('Bad client was accepted and the exception was not thrown');
+    }
+}

+ 318 - 0
vendor/meilisearch/meilisearch-php/tests/Endpoints/DocumentsTest.php

@@ -0,0 +1,318 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Endpoints;
+
+use MeiliSearch\Exceptions\ApiException;
+use MeiliSearch\Exceptions\InvalidArgumentException;
+use Tests\TestCase;
+
+final class DocumentsTest extends TestCase
+{
+    public function testAddDocuments(): void
+    {
+        $index = $this->client->createIndex('documents');
+        $promise = $index->addDocuments(self::DOCUMENTS);
+
+        $this->assertIsValidPromise($promise);
+
+        $index->waitForPendingUpdate($promise['updateId']);
+
+        $response = $index->getDocuments();
+        $this->assertCount(\count(self::DOCUMENTS), $response);
+    }
+
+    public function testGetSingleDocumentWithIntegerDocumentId(): void
+    {
+        $index = $this->client->createIndex('documents');
+        $response = $index->addDocuments(self::DOCUMENTS);
+        $index->waitForPendingUpdate($response['updateId']);
+        $doc = $this->findDocumentWithId(self::DOCUMENTS, 4);
+        $response = $index->getDocument($doc['id']);
+
+        $this->assertIsArray($response);
+        $this->assertSame($doc['id'], $response['id']);
+        $this->assertSame($doc['title'], $response['title']);
+    }
+
+    public function testGetSingleDocumentWithStringDocumentId(): void
+    {
+        $stringDocumentId = 'myUniqueId';
+        $index = $this->client->createIndex('documents');
+        $addDocumentResponse = $index->addDocuments([['id' => $stringDocumentId]]);
+        $index->waitForPendingUpdate($addDocumentResponse['updateId']);
+        $response = $index->getDocument($stringDocumentId);
+
+        $this->assertIsArray($response);
+        $this->assertSame($stringDocumentId, $response['id']);
+    }
+
+    public function testReplaceDocuments(): void
+    {
+        $index = $this->client->createIndex('documents');
+        $response = $index->addDocuments(self::DOCUMENTS);
+        $index->waitForPendingUpdate($response['updateId']);
+        $replacement = [
+            'id' => 2,
+            'title' => 'The Red And The Black',
+        ];
+        $response = $index->addDocuments([$replacement]);
+
+        $this->assertIsValidPromise($response);
+
+        $index->waitForPendingUpdate($response['updateId']);
+        $response = $index->getDocument($replacement['id']);
+
+        $this->assertSame($replacement['id'], $response['id']);
+        $this->assertSame($replacement['title'], $response['title']);
+        $this->assertFalse(array_search('comment', $response));
+        $response = $index->getDocuments();
+        $this->assertCount(\count(self::DOCUMENTS), $response);
+    }
+
+    public function testUpdateDocuments(): void
+    {
+        $index = $this->client->createIndex('documents');
+        $promise = $index->addDocuments(self::DOCUMENTS);
+        $index->waitForPendingUpdate($promise['updateId']);
+        $replacement = [
+            'id' => 456,
+            'title' => 'The Little Prince',
+        ];
+        $promise = $index->updateDocuments([$replacement]);
+
+        $this->assertIsValidPromise($promise);
+
+        $index->waitForPendingUpdate($promise['updateId']);
+        $response = $index->getDocument($replacement['id']);
+
+        $this->assertSame($replacement['id'], $response['id']);
+        $this->assertSame($replacement['title'], $response['title']);
+        $this->assertArrayHasKey('comment', $response);
+
+        $response = $index->getDocuments();
+
+        $this->assertCount(\count(self::DOCUMENTS), $response);
+    }
+
+    public function testAddWithUpdateDocuments(): void
+    {
+        $index = $this->client->createIndex('documents');
+        $response = $index->addDocuments(self::DOCUMENTS);
+        $index->waitForPendingUpdate($response['updateId']);
+        $document = [
+            'id' => 9,
+            'title' => '1984',
+        ];
+        $promise = $index->updateDocuments([$document]);
+
+        $this->assertIsValidPromise($promise);
+
+        $index->waitForPendingUpdate($promise['updateId']);
+        $response = $index->getDocument($document['id']);
+
+        $this->assertSame($document['id'], $response['id']);
+        $this->assertSame($document['title'], $response['title']);
+        $this->assertFalse(array_search('comment', $response));
+
+        $response = $index->getDocuments();
+
+        $this->assertCount(\count(self::DOCUMENTS) + 1, $response);
+    }
+
+    public function testDeleteNonExistingDocument(): void
+    {
+        $index = $this->client->createIndex('documents');
+        $response = $index->addDocuments(self::DOCUMENTS);
+        $index->waitForPendingUpdate($response['updateId']);
+
+        $documentId = 9;
+        $promise = $index->deleteDocument($documentId);
+
+        $this->assertIsValidPromise($promise);
+
+        $index->waitForPendingUpdate($promise['updateId']);
+        $response = $index->getDocuments();
+
+        $this->assertCount(\count(self::DOCUMENTS), $response);
+        $this->assertNull($this->findDocumentWithId($response, $documentId));
+    }
+
+    public function testDeleteSingleExistingDocumentWithDocumentIdAsInteger(): void
+    {
+        $index = $this->client->createIndex('documents');
+        $response = $index->addDocuments(self::DOCUMENTS);
+        $index->waitForPendingUpdate($response['updateId']);
+
+        $documentId = 123;
+        $promise = $index->deleteDocument($documentId);
+
+        $this->assertIsValidPromise($promise);
+
+        $index->waitForPendingUpdate($promise['updateId']);
+        $response = $index->getDocuments();
+
+        $this->assertCount(\count(self::DOCUMENTS) - 1, $response);
+        $this->assertNull($this->findDocumentWithId($response, $documentId));
+    }
+
+    public function testDeleteSingleExistingDocumentWithDocumentIdAsString(): void
+    {
+        $stringDocumentId = 'myUniqueId';
+        $index = $this->client->createIndex('documents');
+        $addDocumentResponse = $index->addDocuments([['id' => $stringDocumentId]]);
+        $index->waitForPendingUpdate($addDocumentResponse['updateId']);
+
+        $promise = $index->deleteDocument($stringDocumentId);
+        $index->waitForPendingUpdate($promise['updateId']);
+
+        $response = $index->getDocuments();
+
+        $this->assertEmpty($response);
+    }
+
+    public function testDeleteMultipleDocumentsWithDocumentIdAsInteger(): void
+    {
+        $index = $this->client->createIndex('documents');
+        $response = $index->addDocuments(self::DOCUMENTS);
+        $index->waitForPendingUpdate($response['updateId']);
+        $documentIds = [1, 2];
+        $promise = $index->deleteDocuments($documentIds);
+
+        $this->assertIsValidPromise($promise);
+
+        $index->waitForPendingUpdate($promise['updateId']);
+        $response = $index->getDocuments();
+
+        $this->assertCount(\count(self::DOCUMENTS) - 2, $response);
+        $this->assertNull($this->findDocumentWithId($response, $documentIds[0]));
+        $this->assertNull($this->findDocumentWithId($response, $documentIds[1]));
+    }
+
+    public function testDeleteMultipleDocumentsWithDocumentIdAsString(): void
+    {
+        $documents = [
+            ['id' => 'myUniqueId1'],
+            ['id' => 'myUniqueId2'],
+            ['id' => 'myUniqueId3'],
+        ];
+        $index = $this->client->createIndex('documents');
+        $addDocumentResponse = $index->addDocuments($documents);
+        $index->waitForPendingUpdate($addDocumentResponse['updateId']);
+
+        $promise = $index->deleteDocuments(['myUniqueId1', 'myUniqueId3']);
+        $index->waitForPendingUpdate($promise['updateId']);
+
+        $response = $index->getDocuments();
+        $this->assertCount(1, $response);
+        $this->assertSame([['id' => 'myUniqueId2']], $response);
+    }
+
+    public function testDeleteAllDocuments(): void
+    {
+        $index = $this->client->createIndex('documents');
+        $response = $index->addDocuments(self::DOCUMENTS);
+        $index->waitForPendingUpdate($response['updateId']);
+        $promise = $index->deleteAllDocuments();
+
+        $this->assertIsValidPromise($promise);
+
+        $index->waitForPendingUpdate($promise['updateId']);
+        $response = $index->getDocuments();
+
+        $this->assertCount(0, $response);
+    }
+
+    public function testExceptionIfNoDocumentIdWhenGetting(): void
+    {
+        $index = $this->client->createIndex('new-index');
+
+        $this->expectException(ApiException::class);
+
+        $index->getDocument(1);
+    }
+
+    public function testAddDocumentWithPrimaryKey(): void
+    {
+        $documents = [
+            [
+                'id' => 1,
+                'unique' => 1,
+                'title' => 'Le Rouge et le Noir',
+            ],
+        ];
+        $index = $this->client->createIndex('an-index');
+        $response = $index->addDocuments($documents, 'unique');
+
+        $this->assertArrayHasKey('updateId', $response);
+        $index->waitForPendingUpdate($response['updateId']);
+
+        $this->assertSame('unique', $index->fetchPrimaryKey());
+        $this->assertCount(1, $index->getDocuments());
+    }
+
+    public function testUpdateDocumentWithPrimaryKey(): void
+    {
+        $documents = [
+            [
+                'id' => 1,
+                'unique' => 1,
+                'title' => 'Le Rouge et le Noir',
+            ],
+        ];
+        $index = $this->client->createIndex('index');
+        $promise = $index->updateDocuments($documents, 'unique');
+
+        $this->assertIsValidPromise($promise);
+
+        $index->waitForPendingUpdate($promise['updateId']);
+
+        $this->assertSame('unique', $index->fetchPrimaryKey());
+        $this->assertCount(1, $index->getDocuments());
+    }
+
+    /**
+     * @dataProvider invalidDocumentIds
+     */
+    public function testFetchingDocumentWithInvalidId($documentId): void
+    {
+        $index = $this->client->createIndex('an-index');
+
+        $this->expectException(InvalidArgumentException::class);
+        $index->getDocument($documentId);
+    }
+
+    /**
+     * @dataProvider invalidDocumentIds
+     */
+    public function testDeletingDocumentWithInvalidId($documentId): void
+    {
+        $index = $this->client->createIndex('an-index');
+
+        $this->expectException(InvalidArgumentException::class);
+        $index->deleteDocument($documentId);
+    }
+
+    public function invalidDocumentIds(): array
+    {
+        return [
+            'documentId as null' => [null],
+            'documentId as bool' => [true],
+            'documentId as empty string' => [''],
+            'documentId as float' => [2.1],
+            'documentId as array' => [[]],
+            'documentId as object' => [new \stdClass()],
+            'documentId as resource' => [tmpfile()],
+        ];
+    }
+
+    private function findDocumentWithId($documents, $documentId)
+    {
+        foreach ($documents as $document) {
+            if ($document['id'] == $documentId) {
+                return $document;
+            }
+        }
+    }
+}

+ 32 - 0
vendor/meilisearch/meilisearch-php/tests/Endpoints/DumpTest.php

@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Endpoints;
+
+use MeiliSearch\Exceptions\ApiException;
+use Tests\TestCase;
+
+final class DumpTest extends TestCase
+{
+    public function testCreateDumpAndGetStatus(): void
+    {
+        $dump = $this->client->createDump();
+
+        $this->assertArrayHasKey('uid', $dump);
+        $this->assertArrayHasKey('status', $dump);
+        $this->assertEquals('in_progress', $dump['status']);
+
+        $dump = $this->client->getDumpStatus($dump['uid']);
+
+        $this->assertArrayHasKey('uid', $dump);
+        $this->assertArrayHasKey('status', $dump);
+    }
+
+    public function testDumpNotFound(): void
+    {
+        $this->expectException(ApiException::class);
+
+        $this->client->getDumpStatus('not-found');
+    }
+}

+ 215 - 0
vendor/meilisearch/meilisearch-php/tests/Endpoints/IndexTest.php

@@ -0,0 +1,215 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Endpoints;
+
+use MeiliSearch\Endpoints\Indexes;
+use MeiliSearch\Exceptions\ApiException;
+use MeiliSearch\Exceptions\TimeOutException;
+use Tests\TestCase;
+
+final class IndexTest extends TestCase
+{
+    private $index;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->index = $this->client->createIndex('index');
+    }
+
+    public function testGetPrimaryKey(): void
+    {
+        $indexB = $this->client->createIndex(
+            'indexB',
+            ['primaryKey' => 'objectId']
+        );
+
+        $this->assertNull($this->index->getPrimaryKey());
+        $this->assertSame('objectId', $indexB->getPrimaryKey());
+    }
+
+    public function testGetUid(): void
+    {
+        $indexB = $this->client->createIndex(
+            'indexB',
+            ['primaryKey' => 'objectId']
+        );
+        $this->assertSame('index', $this->index->getUid());
+        $this->assertSame('indexB', $indexB->getUid());
+    }
+
+    public function testfetchRawInfo(): void
+    {
+        $index = $this->client->createIndex(
+            'indexB',
+            ['primaryKey' => 'objectId']
+        );
+
+        $response = $index->fetchRawInfo();
+
+        $this->assertArrayHasKey('primaryKey', $response);
+        $this->assertArrayHasKey('uid', $response);
+        $this->assertArrayHasKey('createdAt', $response);
+        $this->assertArrayHasKey('updatedAt', $response);
+        $this->assertSame($response['primaryKey'], 'objectId');
+        $this->assertSame($response['uid'], 'indexB');
+    }
+
+    public function testPrimaryKeyUpdate(): void
+    {
+        $primaryKey = 'id';
+
+        $index = $this->index->update(['primaryKey' => $primaryKey]);
+
+        $this->assertInstanceOf(Indexes::class, $index);
+        $this->assertSame($index->getPrimaryKey(), $primaryKey);
+        $this->assertSame($index->getUid(), 'index');
+        $this->assertSame($this->index->getPrimaryKey(), $primaryKey);
+        $this->assertSame($this->index->getUid(), 'index');
+    }
+
+    public function testExceptionIsThrownWhenOverwritingPrimaryKey(): void
+    {
+        $index = $this->client->createIndex(
+            'indexB',
+            ['primaryKey' => 'objectId']
+        );
+
+        $this->expectException(ApiException::class);
+
+        $index->update(['primaryKey' => 'objectID']);
+    }
+
+    public function testIndexStats(): void
+    {
+        $stats = $this->index->stats();
+
+        $this->assertArrayHasKey('numberOfDocuments', $stats);
+        $this->assertEquals(0, $stats['numberOfDocuments']);
+        $this->assertArrayHasKey('isIndexing', $stats);
+        $this->assertArrayHasKey('fieldsDistribution', $stats);
+    }
+
+    public function testFetchInfo(): void
+    {
+        $uid = 'indexA';
+        $this->client->createIndex(
+            $uid,
+            ['primaryKey' => 'objectID']
+        );
+
+        $index = $this->client->index($uid);
+        $this->assertInstanceOf(Indexes::class, $index);
+        $this->assertNull($index->getPrimaryKey());
+
+        $index = $index->fetchInfo();
+        $this->assertInstanceOf(Indexes::class, $index);
+        $this->assertSame('objectID', $index->getPrimaryKey());
+    }
+
+    public function testGetAndFetchPrimaryKey(): void
+    {
+        $uid = 'indexA';
+        $this->client->createIndex(
+            $uid,
+            ['primaryKey' => 'objectID']
+        );
+
+        $index = $this->client->index($uid);
+        $this->assertNull($index->getPrimaryKey());
+        $this->assertSame('objectID', $index->fetchPrimaryKey());
+        $this->assertSame('objectID', $index->getPrimaryKey());
+    }
+
+    public function testWaitForPendingUpdateDefault(): void
+    {
+        $promise = $this->index->addDocuments([['id' => 1, 'title' => 'Pride and Prejudice']]);
+
+        $response = $this->index->waitForPendingUpdate($promise['updateId']);
+
+        $this->assertIsArray($response);
+        $this->assertSame($response['status'], 'processed');
+        $this->assertSame($response['updateId'], $promise['updateId']);
+        $this->assertArrayHasKey('type', $response);
+        $this->assertIsArray($response['type']);
+        $this->assertArrayHasKey('duration', $response);
+        $this->assertArrayHasKey('enqueuedAt', $response);
+        $this->assertArrayHasKey('processedAt', $response);
+    }
+
+    public function testWaitForPendingUpdateWithTimeoutAndInterval(): void
+    {
+        $promise = $this->index->addDocuments([['id' => 1, 'title' => 'Pride and Prejudice']]);
+        $response = $this->index->waitForPendingUpdate($promise['updateId'], 100, 20);
+
+        $this->assertIsArray($response);
+        $this->assertSame($response['status'], 'processed');
+        $this->assertSame($response['updateId'], $promise['updateId']);
+        $this->assertArrayHasKey('type', $response);
+        $this->assertIsArray($response['type']);
+        $this->assertArrayHasKey('duration', $response);
+        $this->assertArrayHasKey('enqueuedAt', $response);
+        $this->assertArrayHasKey('processedAt', $response);
+    }
+
+    public function testWaitForPendingUpdateWithTimeout(): void
+    {
+        $promise = $this->index->addDocuments([['id' => 1, 'title' => 'Pride and Prejudice']]);
+        $response = $this->index->waitForPendingUpdate($promise['updateId'], 100);
+
+        $this->assertIsArray($response);
+        $this->assertSame($response['status'], 'processed');
+        $this->assertSame($response['updateId'], $promise['updateId']);
+        $this->assertArrayHasKey('type', $response);
+        $this->assertIsArray($response['type']);
+        $this->assertArrayHasKey('duration', $response);
+        $this->assertArrayHasKey('enqueuedAt', $response);
+        $this->assertArrayHasKey('processedAt', $response);
+    }
+
+    public function testExceptionWhenPendingUpdateTimeOut(): void
+    {
+        $this->expectException(TimeOutException::class);
+        $res = $this->index->addDocuments([['id' => 1, 'title' => 'Pride and Prejudice']]);
+        $this->index->waitForPendingUpdate($res['updateId'], 0, 20);
+    }
+
+    public function testDeleteIndexes(): void
+    {
+        $this->index = $this->client->createIndex('indexA');
+        $indexB = $this->client->createIndex('indexB');
+
+        $res = $this->index->delete();
+        $this->assertEmpty($res);
+
+        $res = $indexB->delete();
+        $this->assertEmpty($res);
+    }
+
+    public function testExceptionIsThrownIfNoIndexWhenShowing(): void
+    {
+        $this->index->delete();
+
+        $this->expectException(ApiException::class);
+
+        $this->index->fetchInfo();
+    }
+
+    public function testExceptionIsThrownIfNoIndexWhenUpdating(): void
+    {
+        $this->index->delete();
+
+        $this->expectException(ApiException::class);
+        $this->index->update(['primaryKey' => 'objectID']);
+    }
+
+    public function testExceptionIsThrownIfNoIndexWhenDeleting(): void
+    {
+        $this->index->delete();
+
+        $this->expectException(ApiException::class);
+        $this->index->delete();
+    }
+}

+ 74 - 0
vendor/meilisearch/meilisearch-php/tests/Endpoints/KeysAndPermissionsTest.php

@@ -0,0 +1,74 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Endpoints;
+
+use MeiliSearch\Client;
+use MeiliSearch\Exceptions\ApiException;
+use Tests\TestCase;
+
+final class KeysAndPermissionsTest extends TestCase
+{
+    public function testGetKeys(): void
+    {
+        $response = $this->client->getKeys();
+
+        $this->assertArrayHasKey('private', $response);
+        $this->assertArrayHasKey('public', $response);
+        $this->assertIsString($response['private']);
+        $this->assertNotNull($response['private']);
+        $this->assertIsString($response['public']);
+        $this->assertNotNull($response['public']);
+    }
+
+    public function testSearchingIfPublicKeyProvided(): void
+    {
+        $this->client->createIndex('index');
+
+        $newClient = new Client(self::HOST, $this->getKeys()['public']);
+        $response = $newClient->index('index')->search('test');
+        $this->assertArrayHasKey('hits', $response->toArray());
+    }
+
+    public function testGetSettingsIfPrivateKeyProvided(): void
+    {
+        $this->client->createIndex('index');
+        $newClient = new Client(self::HOST, $this->getKeys()['private']);
+        $response = $newClient->index('index')->getSettings();
+
+        $this->assertEquals(['*'], $response['searchableAttributes']);
+    }
+
+    public function testExceptionIfNoMasterKeyProvided(): void
+    {
+        $newClient = new Client(self::HOST);
+
+        $this->expectException(ApiException::class);
+        $newClient->index('index')->search('test');
+    }
+
+    public function testExceptionIfBadKeyProvidedToGetSettings(): void
+    {
+        $this->client->createIndex('index');
+        $response = $this->client->index('index')->getSettings();
+        $this->assertEquals(['*'], $response['searchableAttributes']);
+
+        $newClient = new Client(self::HOST, 'bad-key');
+
+        $this->expectException(ApiException::class);
+        $newClient->index('index')->getSettings();
+    }
+
+    public function testExceptionIfBadKeyProvidedToGetKeys(): void
+    {
+        $this->expectException(ApiException::class);
+        $client = new Client(self::HOST, 'bad-key');
+        $client->getKeys();
+    }
+
+    private function getKeys(): array
+    {
+        return $this->client->getKeys();
+    }
+}

+ 605 - 0
vendor/meilisearch/meilisearch-php/tests/Endpoints/SearchTest.php

@@ -0,0 +1,605 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Endpoints;
+
+use MeiliSearch\Exceptions\ApiException;
+use Tests\TestCase;
+
+final class SearchTest extends TestCase
+{
+    private $index;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->index = $this->client->createIndex('index');
+        $promise = $this->index->updateDocuments(self::DOCUMENTS);
+        $this->index->waitForPendingUpdate($promise['updateId']);
+    }
+
+    public function testBasicSearch(): void
+    {
+        $response = $this->index->search('prince');
+
+        $this->assertArrayHasKey('hits', $response->toArray());
+        $this->assertArrayHasKey('offset', $response->toArray());
+        $this->assertArrayHasKey('limit', $response->toArray());
+        $this->assertArrayHasKey('processingTimeMs', $response->toArray());
+        $this->assertArrayHasKey('query', $response->toArray());
+        $this->assertSame(2, $response->getNbHits());
+        $this->assertCount(2, $response->getHits());
+
+        $response = $this->index->search('prince', [], [
+            'raw' => true,
+        ]);
+
+        $this->assertArrayHasKey('hits', $response);
+        $this->assertArrayHasKey('offset', $response);
+        $this->assertArrayHasKey('limit', $response);
+        $this->assertArrayHasKey('processingTimeMs', $response);
+        $this->assertArrayHasKey('query', $response);
+        $this->assertSame(2, $response['nbHits']);
+    }
+
+    public function testBasicEmptySearch(): void
+    {
+        $response = $this->index->search('');
+
+        $this->assertArrayHasKey('hits', $response->toArray());
+        $this->assertArrayHasKey('offset', $response->toArray());
+        $this->assertArrayHasKey('limit', $response->toArray());
+        $this->assertArrayHasKey('processingTimeMs', $response->toArray());
+        $this->assertArrayHasKey('query', $response->toArray());
+        $this->assertCount(7, $response->getHits());
+
+        $response = $this->index->search('', [], [
+            'raw' => true,
+        ]);
+
+        $this->assertArrayHasKey('hits', $response);
+        $this->assertArrayHasKey('offset', $response);
+        $this->assertArrayHasKey('limit', $response);
+        $this->assertArrayHasKey('processingTimeMs', $response);
+        $this->assertArrayHasKey('query', $response);
+        $this->assertSame(7, $response['nbHits']);
+    }
+
+    public function testBasicPlaceholderSearch(): void
+    {
+        $response = $this->index->search(null);
+
+        $this->assertArrayHasKey('hits', $response->toArray());
+        $this->assertArrayHasKey('offset', $response->toArray());
+        $this->assertArrayHasKey('limit', $response->toArray());
+        $this->assertArrayHasKey('processingTimeMs', $response->toArray());
+        $this->assertArrayHasKey('query', $response->toArray());
+        $this->assertCount(\count(self::DOCUMENTS), $response->getHits());
+
+        $response = $this->index->search(null, [], [
+            'raw' => true,
+        ]);
+
+        $this->assertArrayHasKey('hits', $response);
+        $this->assertArrayHasKey('offset', $response);
+        $this->assertArrayHasKey('limit', $response);
+        $this->assertArrayHasKey('processingTimeMs', $response);
+        $this->assertArrayHasKey('query', $response);
+        $this->assertSame(\count(self::DOCUMENTS), $response['nbHits']);
+    }
+
+    public function testSearchWithOptions(): void
+    {
+        $response = $this->index->search('prince', ['limit' => 1]);
+
+        $this->assertCount(1, $response->getHits());
+
+        $response = $this->index->search('prince', ['limit' => 1], [
+            'raw' => true,
+        ]);
+
+        $this->assertSame(1, \count($response['hits']));
+    }
+
+    public function testBasicSearchIfNoPrimaryKeyAndDocumentProvided(): void
+    {
+        $emptyIndex = $this->client->createIndex('empty');
+
+        $res = $emptyIndex->search('prince');
+
+        $this->assertArrayHasKey('hits', $res->toArray());
+        $this->assertArrayHasKey('offset', $res->toArray());
+        $this->assertArrayHasKey('limit', $res->toArray());
+        $this->assertArrayHasKey('processingTimeMs', $res->toArray());
+        $this->assertArrayHasKey('query', $res->toArray());
+        $this->assertCount(0, $res->getHits());
+
+        $res = $emptyIndex->search('prince', [], [
+            'raw' => true,
+        ]);
+
+        $this->assertArrayHasKey('hits', $res);
+        $this->assertArrayHasKey('offset', $res);
+        $this->assertArrayHasKey('limit', $res);
+        $this->assertArrayHasKey('processingTimeMs', $res);
+        $this->assertArrayHasKey('query', $res);
+        $this->assertSame(0, $res['nbHits']);
+    }
+
+    public function testExceptionIfNoIndexWhenSearching(): void
+    {
+        $index = $this->client->createIndex('another-index');
+        $index->delete();
+
+        $this->expectException(ApiException::class);
+
+        $index->search('prince');
+    }
+
+    public function testParametersArray(): void
+    {
+        $response = $this->index->search('prince', [
+            'limit' => 5,
+            'offset' => 0,
+            'attributesToRetrieve' => ['id', 'title'],
+            'attributesToCrop' => ['id', 'title'],
+            'cropLength' => 6,
+            'attributesToHighlight' => ['title'],
+            'filters' => 'title = "Le Petit Prince"',
+            'matches' => true,
+        ]);
+
+        $this->assertArrayHasKey('_matchesInfo', $response->getHit(0));
+        $this->assertArrayHasKey('title', $response->getHit(0)['_matchesInfo']);
+        $this->assertArrayHasKey('_formatted', $response->getHit(0));
+        $this->assertArrayNotHasKey('comment', $response->getHit(0));
+        $this->assertArrayNotHasKey('comment', $response->getHit(0)['_matchesInfo']);
+        $this->assertSame('Petit <em>Prince</em>', $response->getHit(0)['_formatted']['title']);
+
+        $response = $this->index->search('prince', [
+            'limit' => 5,
+            'offset' => 0,
+            'attributesToRetrieve' => ['id', 'title'],
+            'attributesToCrop' => ['id', 'title'],
+            'cropLength' => 6,
+            'attributesToHighlight' => ['title'],
+            'filters' => 'title = "Le Petit Prince"',
+            'matches' => true,
+        ], [
+            'raw' => true,
+        ]);
+
+        $this->assertArrayHasKey('_matchesInfo', $response['hits'][0]);
+        $this->assertArrayHasKey('title', $response['hits'][0]['_matchesInfo']);
+        $this->assertArrayHasKey('_formatted', $response['hits'][0]);
+        $this->assertArrayNotHasKey('comment', $response['hits'][0]);
+        $this->assertArrayNotHasKey('comment', $response['hits'][0]['_matchesInfo']);
+        $this->assertSame('Petit <em>Prince</em>', $response['hits'][0]['_formatted']['title']);
+    }
+
+    public function testParametersCanBeAStar(): void
+    {
+        $response = $this->index->search('prince', [
+            'limit' => 5,
+            'offset' => 0,
+            'attributesToRetrieve' => ['*'],
+            'attributesToCrop' => ['*'],
+            'cropLength' => 6,
+            'attributesToHighlight' => ['*'],
+            'filters' => 'title = "Le Petit Prince"',
+            'matches' => true,
+        ]);
+
+        $this->assertArrayHasKey('_matchesInfo', $response->getHit(0));
+        $this->assertArrayHasKey('title', $response->getHit(0)['_matchesInfo']);
+        $this->assertArrayHasKey('_formatted', $response->getHit(0));
+        $this->assertArrayHasKey('comment', $response->getHit(0));
+        $this->assertArrayNotHasKey('comment', $response->getHit(0)['_matchesInfo']);
+        $this->assertSame('Petit <em>Prince</em>', $response->getHit(0)['_formatted']['title']);
+
+        $response = $this->index->search('prince', [
+            'limit' => 5,
+            'offset' => 0,
+            'attributesToRetrieve' => ['*'],
+            'attributesToCrop' => ['*'],
+            'cropLength' => 6,
+            'attributesToHighlight' => ['*'],
+            'filters' => 'title = "Le Petit Prince"',
+            'matches' => true,
+        ], [
+            'raw' => true,
+        ]);
+
+        $this->assertArrayHasKey('_matchesInfo', $response['hits'][0]);
+        $this->assertArrayHasKey('title', $response['hits'][0]['_matchesInfo']);
+        $this->assertArrayHasKey('_formatted', $response['hits'][0]);
+        $this->assertArrayHasKey('comment', $response['hits'][0]);
+        $this->assertArrayNotHasKey('comment', $response['hits'][0]['_matchesInfo']);
+        $this->assertSame('Petit <em>Prince</em>', $response['hits'][0]['_formatted']['title']);
+    }
+
+    public function testBasicSearchWithFacetsDistribution(): void
+    {
+        $response = $this->index->updateAttributesForFaceting(['genre']);
+        $this->index->waitForPendingUpdate($response['updateId']);
+
+        $response = $this->index->search('prince', [
+            'facetsDistribution' => ['genre'],
+        ]);
+        $this->assertSame(2, $response->getHitsCount());
+        $this->assertArrayHasKey('facetsDistribution', $response->toArray());
+        $this->assertArrayHasKey('exhaustiveFacetsCount', $response->toArray());
+        $this->assertArrayHasKey('genre', $response->getFacetsDistribution());
+        $this->assertTrue($response->getExhaustiveFacetsCount());
+        $this->assertSame($response->getFacetsDistribution()['genre']['fantasy'], 1);
+        $this->assertSame($response->getFacetsDistribution()['genre']['adventure'], 1);
+        $this->assertSame($response->getFacetsDistribution()['genre']['romance'], 0);
+
+        $response = $this->index->search('prince', [
+            'facetsDistribution' => ['genre'],
+        ], [
+            'raw' => true,
+        ]);
+        $this->assertSame(2, $response['nbHits']);
+        $this->assertArrayHasKey('facetsDistribution', $response);
+        $this->assertArrayHasKey('exhaustiveFacetsCount', $response);
+        $this->assertArrayHasKey('genre', $response['facetsDistribution']);
+        $this->assertTrue($response['exhaustiveFacetsCount']);
+        $this->assertSame($response['facetsDistribution']['genre']['fantasy'], 1);
+        $this->assertSame($response['facetsDistribution']['genre']['adventure'], 1);
+        $this->assertSame($response['facetsDistribution']['genre']['romance'], 0);
+    }
+
+    public function testBasicSearchWithFacetFilters(): void
+    {
+        $response = $this->index->updateAttributesForFaceting(['genre']);
+        $this->index->waitForPendingUpdate($response['updateId']);
+
+        $response = $this->index->search('prince', [
+            'facetFilters' => [['genre:fantasy']],
+        ]);
+        $this->assertSame(1, $response->getHitsCount());
+        $this->assertArrayNotHasKey('facetsDistribution', $response->getRaw());
+        $this->assertArrayNotHasKey('exhaustiveFacetsCount', $response->getRaw());
+        $this->assertSame(4, $response->getHit(0)['id']);
+
+        $response = $this->index->search('prince', [
+            'facetFilters' => [['genre:fantasy']],
+        ], [
+            'raw' => true,
+        ]);
+        $this->assertSame(1, $response['nbHits']);
+        $this->assertArrayNotHasKey('facetsDistribution', $response);
+        $this->assertArrayNotHasKey('exhaustiveFacetsCount', $response);
+        $this->assertSame(4, $response['hits'][0]['id']);
+    }
+
+    public function testBasicSearchWithMultipleFacetFilters(): void
+    {
+        $response = $this->index->updateAttributesForFaceting(['genre']);
+        $this->index->waitForPendingUpdate($response['updateId']);
+
+        $response = $this->index->search('prince', [
+            'facetFilters' => ['genre:fantasy', ['genre:fantasy', 'genre:fantasy']],
+        ]);
+        $this->assertSame(1, $response->getHitsCount());
+        $this->assertArrayNotHasKey('facetsDistribution', $response->getRaw());
+        $this->assertArrayNotHasKey('exhaustiveFacetsCount', $response->getRaw());
+        $this->assertSame(4, $response->getHit(0)['id']);
+
+        $response = $this->index->search('prince', [
+            'facetFilters' => ['genre:fantasy', ['genre:fantasy', 'genre:fantasy']],
+        ], [
+            'raw' => true,
+        ]);
+        $this->assertSame(1, $response['nbHits']);
+        $this->assertArrayNotHasKey('facetsDistribution', $response);
+        $this->assertArrayNotHasKey('exhaustiveFacetsCount', $response);
+        $this->assertSame(4, $response['hits'][0]['id']);
+    }
+
+    public function testCustomSearchWithFacetFiltersAndAttributesToRetrieve(): void
+    {
+        $response = $this->index->updateAttributesForFaceting(['genre']);
+        $this->index->waitForPendingUpdate($response['updateId']);
+
+        $response = $this->index->search('prince', [
+            'facetFilters' => [['genre:fantasy']],
+            'attributesToRetrieve' => ['id', 'title'],
+        ]);
+        $this->assertSame(1, $response->getHitsCount());
+        $this->assertArrayNotHasKey('facetsDistribution', $response->getRaw());
+        $this->assertArrayNotHasKey('exhaustiveFacetsCount', $response->getRaw());
+        $this->assertSame(4, $response->getHit(0)['id']);
+        $this->assertArrayHasKey('id', $response->getHit(0));
+        $this->assertArrayHasKey('title', $response->getHit(0));
+        $this->assertArrayNotHasKey('comment', $response->getHit(0));
+
+        $response = $this->index->search('prince', [
+            'facetFilters' => [['genre:fantasy']],
+            'attributesToRetrieve' => ['id', 'title'],
+        ], [
+            'raw' => true,
+        ]);
+        $this->assertSame(1, $response['nbHits']);
+        $this->assertArrayNotHasKey('facetsDistribution', $response);
+        $this->assertArrayNotHasKey('exhaustiveFacetsCount', $response);
+        $this->assertSame(4, $response['hits'][0]['id']);
+        $this->assertArrayHasKey('id', $response['hits'][0]);
+        $this->assertArrayHasKey('title', $response['hits'][0]);
+        $this->assertArrayNotHasKey('comment', $response['hits'][0]);
+    }
+
+    public function testBasicSerachWithRawSearch(): void
+    {
+        $response = $this->index->rawSearch('prince');
+
+        $this->assertArrayHasKey('hits', $response);
+        $this->assertArrayHasKey('offset', $response);
+        $this->assertArrayHasKey('limit', $response);
+        $this->assertArrayHasKey('processingTimeMs', $response);
+        $this->assertArrayHasKey('query', $response);
+        $this->assertSame(2, $response['nbHits']);
+        $this->assertCount(2, $response['hits']);
+        $this->assertEquals('Le Petit Prince', $response['hits'][0]['title']);
+    }
+
+    public function testBasicSearchWithRawOption(): void
+    {
+        $response = $this->index->search('prince', [], ['raw' => true]);
+
+        $this->assertArrayHasKey('hits', $response);
+        $this->assertArrayHasKey('offset', $response);
+        $this->assertArrayHasKey('limit', $response);
+        $this->assertArrayHasKey('processingTimeMs', $response);
+        $this->assertArrayHasKey('query', $response);
+        $this->assertSame(2, $response['nbHits']);
+        $this->assertCount(2, $response['hits']);
+    }
+
+    public function testBasicSearchWithTransformHitsOptionToFilter(): void
+    {
+        $keepLePetitPrinceFunc = function (array $hits) {
+            return array_filter(
+                $hits,
+                function (array $hit) { return 'Le Petit Prince' === $hit['title']; }
+            );
+        };
+
+        $response = $this->index->search('prince', [], $options = ['transformHits' => $keepLePetitPrinceFunc]);
+
+        $this->assertArrayHasKey('hits', $response->toArray());
+        $this->assertArrayHasKey('offset', $response->toArray());
+        $this->assertArrayHasKey('limit', $response->toArray());
+        $this->assertArrayHasKey('processingTimeMs', $response->toArray());
+        $this->assertArrayHasKey('query', $response->toArray());
+        $this->assertSame('Le Petit Prince', $response->getHit(0)['title']);
+        $this->assertSame(2, $response->getNbHits());
+        $this->assertSame(1, $response->getHitsCount());
+        $this->assertSame(1, $response->count());
+    }
+
+    public function testBasicSearchWithTransformHitsOptionToMap(): void
+    {
+        $titlesToUpperCaseFunc = function (array $hits) {
+            return array_map(
+                function (array $hit) {
+                    $hit['title'] = strtoupper($hit['title']);
+
+                    return $hit;
+                },
+                $hits
+            );
+        };
+
+        $response = $this->index->search('prince', [], ['transformHits' => $titlesToUpperCaseFunc]);
+
+        $this->assertArrayHasKey('hits', $response->toArray());
+        $this->assertArrayHasKey('offset', $response->toArray());
+        $this->assertArrayHasKey('limit', $response->toArray());
+        $this->assertArrayHasKey('processingTimeMs', $response->toArray());
+        $this->assertArrayHasKey('query', $response->toArray());
+        $this->assertSame(2, $response->getNbHits());
+        $this->assertSame(2, $response->getHitsCount());
+        $this->assertCount(2, $response->getHits());
+        $this->assertSame('LE PETIT PRINCE', $response->getHits()[0]['title']);
+    }
+
+    public function testBasicSearchCannotBeFilteredOnRawResult(): void
+    {
+        $keepLePetitPrinceFunc = function (array $hits) {
+            return array_filter(
+                $hits,
+                function (array $hit) { return 'Le Petit Prince' === $hit['title']; }
+            );
+        };
+
+        $response = $this->index->search('prince', [], [
+            'raw' => true,
+            'transformHits' => $keepLePetitPrinceFunc,
+        ]);
+
+        $this->assertArrayHasKey('hits', $response);
+        $this->assertArrayHasKey('offset', $response);
+        $this->assertArrayHasKey('limit', $response);
+        $this->assertArrayHasKey('processingTimeMs', $response);
+        $this->assertArrayHasKey('query', $response);
+        $this->assertSame(2, $response['nbHits']);
+        $this->assertCount(2, $response['hits']);
+    }
+
+    public function testBasicSearchCanBeFilteredOnRawResultIfUsingToArray(): void
+    {
+        $keepLePetitPrinceFunc = function (array $hits) {
+            return array_filter(
+                $hits,
+                function (array $hit) { return 'Le Petit Prince' === $hit['title']; }
+            );
+        };
+
+        $response = $this->index->search('prince', [], ['transformHits' => $keepLePetitPrinceFunc])->toArray();
+
+        $this->assertArrayHasKey('hits', $response);
+        $this->assertArrayHasKey('offset', $response);
+        $this->assertArrayHasKey('limit', $response);
+        $this->assertArrayHasKey('processingTimeMs', $response);
+        $this->assertArrayHasKey('query', $response);
+        $this->assertSame(2, $response['nbHits']);
+        $this->assertCount(1, $response['hits']);
+        $this->assertEquals('Le Petit Prince', $response['hits'][0]['title']);
+    }
+
+    public function testBasicSearchWithRemoveZeroFacetsOption(): void
+    {
+        $response = $this->index->updateAttributesForFaceting(['genre']);
+        $this->index->waitForPendingUpdate($response['updateId']);
+
+        $response = $this->index->search(
+            'prince',
+            ['facetsDistribution' => ['genre']],
+            ['removeZeroFacets' => true]
+        );
+
+        $this->assertCount(2, $response->getFacetsDistribution()['genre']);
+        $this->assertEquals(1, $response->getFacetsDistribution()['genre']['adventure']);
+        $this->assertEquals(1, $response->getFacetsDistribution()['genre']['fantasy']);
+        $this->assertCount(3, $response->getRaw()['facetsDistribution']['genre']);
+        $this->assertEquals($response->getRaw()['hits'], $response->getHits());
+        $this->assertNotEquals($response->getRaw()['facetsDistribution'], $response->getFacetsDistribution());
+    }
+
+    public function testBasicSearchWithRemoveZeroFacetsOptionAndMultipleFacets(): void
+    {
+        $response = $this->index->addDocuments([['id' => 32, 'title' => 'The Witcher', 'genre' => 'adventure', 'adaptation' => 'video game']]);
+        $this->index->waitForPendingUpdate($response['updateId']);
+        $response = $this->index->updateAttributesForFaceting(['genre', 'adaptation']);
+        $this->index->waitForPendingUpdate($response['updateId']);
+
+        $response = $this->index->search(
+            'prince',
+            ['facetsDistribution' => ['genre', 'adaptation']],
+            ['removeZeroFacets' => true]
+        );
+
+        $this->assertCount(2, $response->getFacetsDistribution()['genre']);
+        $this->assertEquals(1, $response->getFacetsDistribution()['genre']['adventure']);
+        $this->assertEquals(1, $response->getFacetsDistribution()['genre']['fantasy']);
+        $this->assertEquals([], $response->getFacetsDistribution()['adaptation']);
+        $this->assertCount(1, $response->getRaw()['facetsDistribution']['adaptation']);
+        $this->assertCount(3, $response->getRaw()['facetsDistribution']['genre']);
+        $this->assertEquals($response->getRaw()['hits'], $response->getHits());
+        $this->assertNotEquals($response->getRaw()['facetsDistribution'], $response->getFacetsDistribution());
+    }
+
+    public function testBasicSearchWithTransformFacetsDritributionOptionToFilter(): void
+    {
+        $response = $this->index->updateAttributesForFaceting(['genre']);
+        $this->index->waitForPendingUpdate($response['updateId']);
+
+        $filterAllFacets = function (array $facets) {
+            $filterOneFacet = function (array $facet) {
+                return array_filter(
+                    $facet,
+                    function ($v, $k) { return $v > 1; },
+                    ARRAY_FILTER_USE_BOTH
+                );
+            };
+
+            return array_map($filterOneFacet, $facets);
+        };
+
+        $response = $this->index->search(
+            null,
+            ['facetsDistribution' => ['genre']],
+            ['transformFacetsDistribution' => $filterAllFacets]
+        );
+
+        $this->assertArrayHasKey('hits', $response->toArray());
+        $this->assertArrayHasKey('facetsDistribution', $response->toArray());
+        $this->assertArrayHasKey('offset', $response->toArray());
+        $this->assertArrayHasKey('limit', $response->toArray());
+        $this->assertArrayHasKey('processingTimeMs', $response->toArray());
+        $this->assertArrayHasKey('query', $response->toArray());
+        $this->assertEquals($response->getRaw()['hits'], $response->getHits());
+        $this->assertNotEquals($response->getRaw()['facetsDistribution'], $response->getFacetsDistribution());
+        $this->assertCount(3, $response->getRaw()['facetsDistribution']['genre']);
+        $this->assertCount(2, $response->getFacetsDistribution()['genre']);
+        $this->assertEquals(3, $response->getFacetsDistribution()['genre']['romance']);
+        $this->assertEquals(2, $response->getFacetsDistribution()['genre']['fantasy']);
+    }
+
+    public function testBasicSearchWithTransformFacetsDritributionOptionToMap(): void
+    {
+        $response = $this->index->updateAttributesForFaceting(['genre']);
+        $this->index->waitForPendingUpdate($response['updateId']);
+
+        $facetsToUpperFunc = function (array $facets) {
+            $changeOneFacet = function (array $facet) {
+                $result = [];
+                foreach ($facet as $k => $v) {
+                    $result[strtoupper($k)] = $v;
+                }
+
+                return $result;
+            };
+
+            return array_map($changeOneFacet, $facets);
+        };
+
+        $response = $this->index->search(
+            null,
+            ['facetsDistribution' => ['genre']],
+            ['transformFacetsDistribution' => $facetsToUpperFunc]
+        );
+
+        $this->assertArrayHasKey('hits', $response->toArray());
+        $this->assertArrayHasKey('facetsDistribution', $response->toArray());
+        $this->assertArrayHasKey('offset', $response->toArray());
+        $this->assertArrayHasKey('limit', $response->toArray());
+        $this->assertArrayHasKey('processingTimeMs', $response->toArray());
+        $this->assertArrayHasKey('query', $response->toArray());
+        $this->assertEquals($response->getRaw()['hits'], $response->getHits());
+        $this->assertNotEquals($response->getRaw()['facetsDistribution'], $response->getFacetsDistribution());
+        $this->assertCount(3, $response->getFacetsDistribution()['genre']);
+        $this->assertEquals(3, $response->getFacetsDistribution()['genre']['ROMANCE']);
+        $this->assertEquals(2, $response->getFacetsDistribution()['genre']['FANTASY']);
+        $this->assertEquals(1, $response->getFacetsDistribution()['genre']['ADVENTURE']);
+    }
+
+    public function testBasicSearchWithTransformFacetsDritributionOptionToOder(): void
+    {
+        $response = $this->index->updateAttributesForFaceting(['genre']);
+        $this->index->waitForPendingUpdate($response['updateId']);
+
+        $facetsToUpperFunc = function (array $facets) {
+            $sortOneFacet = function (array $facet) {
+                ksort($facet);
+
+                return $facet;
+            };
+
+            return array_map($sortOneFacet, $facets);
+        };
+
+        $response = $this->index->search(
+            null,
+            ['facetsDistribution' => ['genre']],
+            ['transformFacetsDistribution' => $facetsToUpperFunc]
+        );
+
+        $this->assertArrayHasKey('hits', $response->toArray());
+        $this->assertArrayHasKey('facetsDistribution', $response->toArray());
+        $this->assertArrayHasKey('offset', $response->toArray());
+        $this->assertArrayHasKey('limit', $response->toArray());
+        $this->assertArrayHasKey('processingTimeMs', $response->toArray());
+        $this->assertArrayHasKey('query', $response->toArray());
+        $this->assertEquals($response->getRaw()['hits'], $response->getHits());
+        $this->assertEquals('adventure', array_key_first($response->getFacetsDistribution()['genre']));
+        $this->assertEquals('romance', array_key_last($response->getFacetsDistribution()['genre']));
+        $this->assertCount(3, $response->getFacetsDistribution()['genre']);
+        $this->assertEquals(3, $response->getFacetsDistribution()['genre']['romance']);
+        $this->assertEquals(2, $response->getFacetsDistribution()['genre']['fantasy']);
+        $this->assertEquals(1, $response->getFacetsDistribution()['genre']['adventure']);
+    }
+}

+ 63 - 0
vendor/meilisearch/meilisearch-php/tests/Endpoints/UpdatesTest.php

@@ -0,0 +1,63 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Endpoints;
+
+use MeiliSearch\Exceptions\ApiException;
+use Tests\TestCase;
+
+final class UpdatesTest extends TestCase
+{
+    private $index;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->index = $this->client->createIndex('index');
+    }
+
+    public function testGetOneUpdate(): void
+    {
+        [$promise, $response] = $this->seedIndex();
+
+        $this->assertIsArray($response);
+        $this->assertSame($response['status'], 'processed');
+        $this->assertSame($response['updateId'], $promise['updateId']);
+        $this->assertArrayHasKey('type', $response);
+        $this->assertIsArray($response['type']);
+        $this->assertArrayHasKey('duration', $response);
+        $this->assertArrayHasKey('enqueuedAt', $response);
+        $this->assertArrayHasKey('processedAt', $response);
+    }
+
+    public function testGetAllUpdates(): void
+    {
+        $this->seedIndex();
+
+        $response = $this->index->getAllUpdateStatus();
+
+        $this->assertCount(1, $response);
+        $this->assertSame('processed', $response[0]['status']);
+        $this->assertArrayHasKey('updateId', $response[0]);
+        $this->assertArrayHasKey('type', $response[0]);
+        $this->assertIsArray($response[0]['type']);
+        $this->assertArrayHasKey('duration', $response[0]);
+        $this->assertArrayHasKey('enqueuedAt', $response[0]);
+        $this->assertArrayHasKey('processedAt', $response[0]);
+    }
+
+    public function testExceptionIfNoUpdateIdWhenGetting(): void
+    {
+        $this->expectException(ApiException::class);
+        $this->index->getUpdateStatus(10000);
+    }
+
+    private function seedIndex(): array
+    {
+        $promise = $this->index->updateDocuments(self::DOCUMENTS);
+        $response = $this->index->waitForPendingUpdate($promise['updateId']);
+
+        return [$promise, $response];
+    }
+}

+ 36 - 0
vendor/meilisearch/meilisearch-php/tests/Exceptions/ApiExceptionTest.php

@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Exceptions;
+
+use MeiliSearch\Exceptions\ApiException;
+use Tests\TestCase;
+
+final class ApiExceptionTest extends TestCase
+{
+    public function testException(): void
+    {
+        $httpBodyExample = [
+            'message' => 'This is the message',
+            'errorCode' => 'this_is_the_error_code',
+            'errorType' => 'this_is_the_error_type',
+            'errorLink' => 'https://docs.meilisearch.com/errors',
+        ];
+        $statusCode = 400;
+
+        try {
+            throw new ApiException($statusCode, $httpBodyExample);
+        } catch (ApiException $apiException) {
+            $this->assertEquals($statusCode, $apiException->httpStatus);
+            $this->assertEquals($httpBodyExample['message'],
+                $apiException->message);
+            $this->assertEquals($httpBodyExample['errorCode'], $apiException->errorCode);
+            $this->assertEquals($httpBodyExample['errorType'], $apiException->errorType);
+            $this->assertEquals($httpBodyExample['errorLink'], $apiException->errorLink);
+
+            $expectedExceptionToString = "MeiliSearch HTTPRequestException: Http Status: {$statusCode} - Message: {$httpBodyExample['message']} - Error code: {$httpBodyExample['errorCode']} - Error type: {$httpBodyExample['errorType']} - Error link: {$httpBodyExample['errorLink']}";
+            $this->assertEquals($expectedExceptionToString, (string) $apiException);
+        }
+    }
+}

+ 20 - 0
vendor/meilisearch/meilisearch-php/tests/Http/ClientTest.php

@@ -0,0 +1,20 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Http;
+
+use MeiliSearch\Client;
+use MeiliSearch\Exceptions\CommunicationException;
+use Tests\TestCase;
+
+class ClientTest extends TestCase
+{
+    public function testThrowCommunicationException(): void
+    {
+        $client = new Client('http://wrongurl:1234');
+
+        $this->expectException(CommunicationException::class);
+        $client->createIndex('some_index');
+    }
+}

+ 227 - 0
vendor/meilisearch/meilisearch-php/tests/Search/SearchResultTest.php

@@ -0,0 +1,227 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Search;
+
+use MeiliSearch\Search\SearchResult;
+use PHPUnit\Framework\TestCase;
+use function strtoupper;
+
+final class SearchResultTest extends TestCase
+{
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->basicServerResponse = [
+            'hits' => [
+                [
+                    'id' => '1',
+                    'title' => 'American Pie 2',
+                    'poster' => 'https://image.tmdb.org/t/p/w1280/q4LNgUnRfltxzp3gf1MAGiK5LhV.jpg',
+                    'overview' => 'The whole gang are back and as close as ever. They decide to get even closer by spending the summer together at a beach house. They decide to hold the biggest...',
+                    'release_date' => 997405200,
+                ],
+                [
+                    'id' => '190859',
+                    'title' => 'American Sniper',
+                    'poster' => 'https://image.tmdb.org/t/p/w1280/svPHnYE7N5NAGO49dBmRhq0vDQ3.jpg',
+                    'overview' => 'U.S. Navy SEAL Chris Kyle takes his sole mission—protect his comrades—to heart and becomes one of the most lethal snipers in American history. His pinpoint accuracy not only saves countless lives but also makes him a prime...',
+                    'release_date' => 1418256000,
+                ],
+            ],
+            'offset' => 0,
+            'limit' => 20,
+            'nbHits' => 976,
+            'exhaustiveNbHits' => false,
+            'processingTimeMs' => 35,
+            'query' => 'american',
+        ];
+        $this->basicResult = new SearchResult($this->basicServerResponse);
+        $this->serverResponseWithFacets = [
+            'hits' => [
+                [
+                    'genre' => 'adventure',
+                    'id' => 456,
+                    'title' => 'Le Petit Prince',
+                    'author' => 'Antoine de Saint-Exupéry',
+                ],
+                [
+                    'genre' => 'fantasy',
+                    'id' => 4,
+                    'title' => 'Harry Potter and the Half-Blood Prince',
+                    'author' => 'J. K. Rowling',
+                ],
+            ],
+            'offset' => 0,
+            'limit' => 20,
+            'nbHits' => 2,
+            'exhaustiveNbHits' => false,
+            'processingTimeMs' => 1,
+            'query' => 'prinec',
+            'facetsDistribution' => [
+                'genre' => [
+                    'fantasy' => 1,
+                    'romance' => 0,
+                    'adventure' => 1,
+                ],
+            ],
+            'exhaustiveFacetsCount' => true,
+        ];
+        $this->resultWithFacets = new SearchResult($this->serverResponseWithFacets);
+    }
+
+    public function testResultCanBeBuilt(): void
+    {
+        $this->assertSame(2, $this->basicResult->count());
+        $this->assertNotEmpty($this->basicResult->getHits());
+
+        $this->assertEquals([
+            'id' => '1',
+            'title' => 'American Pie 2',
+            'poster' => 'https://image.tmdb.org/t/p/w1280/q4LNgUnRfltxzp3gf1MAGiK5LhV.jpg',
+            'overview' => 'The whole gang are back and as close as ever. They decide to get even closer by spending the summer together at a beach house. They decide to hold the biggest...',
+            'release_date' => 997405200,
+        ], $this->basicResult->getHit(0));
+        $this->assertEquals([
+            'id' => '190859',
+            'title' => 'American Sniper',
+            'poster' => 'https://image.tmdb.org/t/p/w1280/svPHnYE7N5NAGO49dBmRhq0vDQ3.jpg',
+            'overview' => 'U.S. Navy SEAL Chris Kyle takes his sole mission—protect his comrades—to heart and becomes one of the most lethal snipers in American history. His pinpoint accuracy not only saves countless lives but also makes him a prime...',
+            'release_date' => 1418256000,
+        ], $this->basicResult->getHit(1));
+        $this->assertNull($this->basicResult->getHit(2));
+        $this->assertSame(0, $this->basicResult->getOffset());
+        $this->assertSame(20, $this->basicResult->getLimit());
+        $this->assertSame(2, $this->basicResult->getHitsCount());
+        $this->assertSame(976, $this->basicResult->getNbHits());
+        $this->assertFalse($this->basicResult->getExhaustiveNbHits());
+        $this->assertSame(35, $this->basicResult->getProcessingTimeMs());
+        $this->assertSame('american', $this->basicResult->getQuery());
+        $this->assertNull($this->basicResult->getExhaustiveFacetsCount());
+        $this->assertEmpty($this->basicResult->getFacetsDistribution());
+        $this->assertSame(2, $this->basicResult->getIterator()->count());
+
+        $this->assertArrayHasKey('hits', $this->basicResult->toArray());
+        $this->assertArrayHasKey('offset', $this->basicResult->toArray());
+        $this->assertArrayHasKey('limit', $this->basicResult->toArray());
+        $this->assertArrayHasKey('nbHits', $this->basicResult->toArray());
+        $this->assertArrayHasKey('hitsCount', $this->basicResult->toArray());
+        $this->assertArrayHasKey('exhaustiveNbHits', $this->basicResult->toArray());
+        $this->assertArrayHasKey('processingTimeMs', $this->basicResult->toArray());
+        $this->assertArrayHasKey('query', $this->basicResult->toArray());
+        $this->assertArrayHasKey('exhaustiveFacetsCount', $this->basicResult->toArray());
+        $this->assertArrayHasKey('facetsDistribution', $this->basicResult->toArray());
+    }
+
+    public function testSearchResultCanBeFiltered(): void
+    {
+        $keepAmericanSniperFunc = function (array $hits) {
+            return array_filter(
+                $hits,
+                function (array $hit) { return 'American Sniper' === $hit['title']; }
+            );
+        };
+
+        $options = ['transformHits' => $keepAmericanSniperFunc];
+
+        $filteredResults = $this->basicResult->applyOptions($options);
+
+        $this->assertSame(1, $filteredResults->count());
+        $this->assertSame(1, $filteredResults->getHitsCount());
+        $this->assertSame(976, $filteredResults->getNbHits());
+        $this->assertEquals([
+            'id' => '190859',
+            'title' => 'American Sniper',
+            'poster' => 'https://image.tmdb.org/t/p/w1280/svPHnYE7N5NAGO49dBmRhq0vDQ3.jpg',
+            'overview' => 'U.S. Navy SEAL Chris Kyle takes his sole mission—protect his comrades—to heart and becomes one of the most lethal snipers in American history. His pinpoint accuracy not only saves countless lives but also makes him a prime...',
+            'release_date' => 1418256000,
+        ], $filteredResults->getHit(1)); // Not getHits(0) because array_filter() does not reorder the indexes after filtering.
+    }
+
+    public function testResultCanBeReturnedAsJson(): void
+    {
+        $json = $this->basicResult->toJSON();
+
+        $this->assertStringContainsString('hits', $json);
+        $this->assertStringContainsString('offset', $json);
+        $this->assertStringContainsString('limit', $json);
+        $this->assertStringContainsString('hitsCount', $json);
+        $this->assertStringContainsString('nbHits', $json);
+        $this->assertStringContainsString('exhaustiveNbHits', $json);
+        $this->assertStringContainsString('processingTimeMs', $json);
+        $this->assertStringContainsString('query', $json);
+        $this->assertStringContainsString('exhaustiveFacetsCount', $json);
+        $this->assertStringContainsString('facetsDistribution', $json);
+    }
+
+    public function testGetRaw(): void
+    {
+        $this->assertEquals($this->basicServerResponse, $this->basicResult->getRaw());
+    }
+
+    public function testTransformHitsMethod(): void
+    {
+        $keepAmericanSniperFunc = function (array $hits) {
+            return array_filter(
+                $hits,
+                function (array $hit) { return 'American Sniper' === $hit['title']; }
+            );
+        };
+
+        $response = $this->basicResult->transformHits($keepAmericanSniperFunc);
+
+        $this->assertArrayHasKey('hits', $response->toArray());
+        $this->assertArrayHasKey('offset', $response->toArray());
+        $this->assertArrayHasKey('limit', $response->toArray());
+        $this->assertArrayHasKey('processingTimeMs', $response->toArray());
+        $this->assertArrayHasKey('query', $response->toArray());
+        $this->assertSame('American Sniper', $response->getHit(1)['title']); // Not getHits(0) because array_filter() does not reorder the indexes after filtering.
+        $this->assertSame(976, $response->getNbHits());
+        $this->assertSame(1, $response->getHitsCount());
+        $this->assertSame(1, $response->count());
+    }
+
+    public function testTransformFacetsDritributionMethod(): void
+    {
+        $facetsToUpperFunc = function (array $facets) {
+            $changeOneFacet = function (array $facet) {
+                $result = [];
+                foreach ($facet as $k => $v) {
+                    $result[strtoupper($k)] = $v;
+                }
+
+                return $result;
+            };
+
+            return array_map($changeOneFacet, $facets);
+        };
+
+        $response = $this->resultWithFacets->transformFacetsDistribution($facetsToUpperFunc);
+
+        $this->assertArrayHasKey('hits', $response->toArray());
+        $this->assertArrayHasKey('facetsDistribution', $response->toArray());
+        $this->assertArrayHasKey('offset', $response->toArray());
+        $this->assertArrayHasKey('limit', $response->toArray());
+        $this->assertArrayHasKey('processingTimeMs', $response->toArray());
+        $this->assertArrayHasKey('query', $response->toArray());
+        $this->assertEquals($response->getRaw()['hits'], $response->getHits());
+        $this->assertNotEquals($response->getRaw()['facetsDistribution'], $response->getFacetsDistribution());
+        $this->assertCount(3, $response->getFacetsDistribution()['genre']);
+        $this->assertEquals(0, $response->getFacetsDistribution()['genre']['ROMANCE']);
+        $this->assertEquals(1, $response->getFacetsDistribution()['genre']['FANTASY']);
+        $this->assertEquals(1, $response->getFacetsDistribution()['genre']['ADVENTURE']);
+    }
+
+    public function testRemoveZeroFacetsMethod(): void
+    {
+        $response = $this->resultWithFacets->removeZeroFacets();
+
+        $this->assertCount(2, $response->getFacetsDistribution()['genre']);
+        $this->assertEquals(1, $response->getFacetsDistribution()['genre']['adventure']);
+        $this->assertEquals(1, $response->getFacetsDistribution()['genre']['fantasy']);
+        $this->assertCount(3, $response->getRaw()['facetsDistribution']['genre']);
+        $this->assertEquals($response->getRaw()['hits'], $response->getHits());
+        $this->assertNotEquals($response->getRaw()['facetsDistribution'], $response->getFacetsDistribution());
+    }
+}

+ 55 - 0
vendor/meilisearch/meilisearch-php/tests/Settings/AttributesForFacetingTest.php

@@ -0,0 +1,55 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Settings;
+
+use Tests\TestCase;
+
+final class AttributesForFacetingTest extends TestCase
+{
+    public function testGetDefaultAttributesForFaceting(): void
+    {
+        $index = $this->client->createIndex('index');
+
+        $attributes = $index->getAttributesForFaceting();
+
+        $this->assertIsArray($attributes);
+        $this->assertEmpty($attributes);
+    }
+
+    public function testUpdateAttributesForFaceting(): void
+    {
+        $newAttributes = ['title'];
+        $index = $this->client->createIndex('index');
+
+        $promise = $index->updateAttributesForFaceting($newAttributes);
+
+        $this->assertIsValidPromise($promise);
+        $index->waitForPendingUpdate($promise['updateId']);
+
+        $attributesForFaceting = $index->getAttributesForFaceting();
+
+        $this->assertIsArray($attributesForFaceting);
+        $this->assertEquals($newAttributes, $attributesForFaceting);
+    }
+
+    public function testResetAttributesForFaceting(): void
+    {
+        $index = $this->client->createIndex('index');
+        $newAttributes = ['title'];
+
+        $promise = $index->updateAttributesForFaceting($newAttributes);
+        $index->waitForPendingUpdate($promise['updateId']);
+
+        $promise = $index->resetAttributesForFaceting();
+
+        $this->assertIsValidPromise($promise);
+
+        $index->waitForPendingUpdate($promise['updateId']);
+
+        $attributesForFaceting = $index->getAttributesForFaceting();
+        $this->assertIsArray($attributesForFaceting);
+        $this->assertEmpty($attributesForFaceting);
+    }
+}

+ 60 - 0
vendor/meilisearch/meilisearch-php/tests/Settings/DisplayedAttributesTest.php

@@ -0,0 +1,60 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Settings;
+
+use Tests\TestCase;
+
+final class DisplayedAttributesTest extends TestCase
+{
+    public function testGetDefaultDisplayedAttributes(): void
+    {
+        $indexA = $this->client->createIndex('indexA');
+        $indexB = $this->client->createIndex('indexB', ['primaryKey' => 'objectID']);
+
+        $attributesA = $indexA->getDisplayedAttributes();
+        $attributesB = $indexB->getDisplayedAttributes();
+
+        $this->assertIsArray($attributesA);
+        $this->assertEquals(['*'], $attributesA);
+
+        $this->assertIsArray($attributesB);
+        $this->assertEquals(['*'], $attributesB);
+    }
+
+    public function testUpdateDisplayedAttributes(): void
+    {
+        $newAttributes = ['title'];
+        $index = $this->client->createIndex('index');
+
+        $promise = $index->updateDisplayedAttributes($newAttributes);
+
+        $this->assertIsValidPromise($promise);
+        $index->waitForPendingUpdate($promise['updateId']);
+
+        $displayedAttributes = $index->getDisplayedAttributes();
+
+        $this->assertIsArray($displayedAttributes);
+        $this->assertEquals($newAttributes, $displayedAttributes);
+    }
+
+    public function testResetDisplayedAttributes(): void
+    {
+        $index = $this->client->createIndex('index');
+        $newAttributes = ['title'];
+
+        $promise = $index->updateDisplayedAttributes($newAttributes);
+        $index->waitForPendingUpdate($promise['updateId']);
+
+        $promise = $index->resetDisplayedAttributes();
+
+        $this->assertIsValidPromise($promise);
+
+        $index->waitForPendingUpdate($promise['updateId']);
+
+        $displayedAttributes = $index->getDisplayedAttributes();
+        $this->assertIsArray($displayedAttributes);
+        $this->assertEquals(['*'], $displayedAttributes);
+    }
+}

+ 48 - 0
vendor/meilisearch/meilisearch-php/tests/Settings/DistinctAttributeTest.php

@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Settings;
+
+use Tests\TestCase;
+
+final class DistinctAttributeTest extends TestCase
+{
+    private $index;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->index = $this->client->createIndex('index');
+    }
+
+    public function testGetDefaultDistinctAttribute(): void
+    {
+        $response = $this->index->getDistinctAttribute();
+        $this->assertNull($response);
+    }
+
+    public function testUpdateDistinctAttribute(): void
+    {
+        $distinctAttribute = 'description';
+        $promise = $this->index->updateDistinctAttribute($distinctAttribute);
+
+        $this->assertIsValidPromise($promise);
+        $this->index->waitForPendingUpdate($promise['updateId']);
+
+        $this->assertEquals($distinctAttribute, $this->index->getDistinctAttribute());
+    }
+
+    public function testResetDistinctAttribute(): void
+    {
+        $distinctAttribute = 'description';
+        $promise = $this->index->updateDistinctAttribute($distinctAttribute);
+        $this->index->waitForPendingUpdate($promise['updateId']);
+
+        $promise = $this->index->resetDistinctAttribute();
+
+        $this->assertIsValidPromise($promise);
+        $this->index->waitForPendingUpdate($promise['updateId']);
+        $this->assertNull($this->index->getDistinctAttribute());
+    }
+}

+ 67 - 0
vendor/meilisearch/meilisearch-php/tests/Settings/RankingRulesTest.php

@@ -0,0 +1,67 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Settings;
+
+use Tests\TestCase;
+
+final class RankingRulesTest extends TestCase
+{
+    private $index;
+
+    const DEFAULT_RANKING_RULES = [
+        'typo',
+        'words',
+        'proximity',
+        'attribute',
+        'wordsPosition',
+        'exactness',
+    ];
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->index = $this->client->createIndex('index');
+    }
+
+    public function testGetDefaultRankingRules(): void
+    {
+        $response = $this->index->getRankingRules();
+
+        $this->assertIsArray($response);
+        $this->assertEquals(self::DEFAULT_RANKING_RULES, $response);
+    }
+
+    public function testUpdateRankingRules(): void
+    {
+        $newRankingRules = [
+            'asc(title)',
+            'typo',
+            'desc(description)',
+        ];
+
+        $promise = $this->index->updateRankingRules($newRankingRules);
+
+        $this->assertIsValidPromise($promise);
+        $this->index->waitForPendingUpdate($promise['updateId']);
+
+        $rankingRules = $this->index->getRankingRules();
+
+        $this->assertIsArray($rankingRules);
+        $this->assertEquals($newRankingRules, $rankingRules);
+    }
+
+    public function testResetRankingRules(): void
+    {
+        $promise = $this->index->resetRankingRules();
+
+        $this->assertIsValidPromise($promise);
+
+        $this->index->waitForPendingUpdate($promise['updateId']);
+        $rankingRules = $this->index->getRankingRules();
+
+        $this->assertIsArray($rankingRules);
+        $this->assertEquals(self::DEFAULT_RANKING_RULES, $rankingRules);
+    }
+}

+ 57 - 0
vendor/meilisearch/meilisearch-php/tests/Settings/SearchableAttributesTest.php

@@ -0,0 +1,57 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Settings;
+
+use Tests\TestCase;
+
+final class SearchableAttributesTest extends TestCase
+{
+    public function testGetDefaultSearchableAttributes(): void
+    {
+        $indexA = $this->client->createIndex('indexA');
+        $indexB = $this->client->createIndex('indexB', ['primaryKey' => 'objectID']);
+
+        $searchableAttributesA = $indexA->getSearchableAttributes();
+        $searchableAttributesB = $indexB->getSearchableAttributes();
+
+        $this->assertIsArray($searchableAttributesA);
+        $this->assertEquals(['*'], $searchableAttributesA);
+        $this->assertIsArray($searchableAttributesB);
+        $this->assertEquals(['*'], $searchableAttributesB);
+    }
+
+    public function testUpdateSearchableAttributes(): void
+    {
+        $indexA = $this->client->createIndex('indexA');
+        $searchableAttributes = [
+            'title',
+            'description',
+        ];
+
+        $promise = $indexA->updateSearchableAttributes($searchableAttributes);
+
+        $this->assertIsValidPromise($promise);
+
+        $indexA->waitForPendingUpdate($promise['updateId']);
+        $updatedAttributes = $indexA->getSearchableAttributes();
+
+        $this->assertIsArray($updatedAttributes);
+        $this->assertEquals($searchableAttributes, $updatedAttributes);
+    }
+
+    public function testResetSearchableAttributes(): void
+    {
+        $index = $this->client->createIndex('indexA');
+        $promise = $index->resetSearchableAttributes();
+
+        $this->assertIsValidPromise($promise);
+
+        $index->waitForPendingUpdate($promise['updateId']);
+        $searchableAttributes = $index->getSearchableAttributes();
+
+        $this->assertIsArray($searchableAttributes);
+        $this->assertEquals(['*'], $searchableAttributes);
+    }
+}

+ 140 - 0
vendor/meilisearch/meilisearch-php/tests/Settings/SettingsTest.php

@@ -0,0 +1,140 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Settings;
+
+use Tests\TestCase;
+
+final class SettingsTest extends TestCase
+{
+    const DEFAULT_RANKING_RULES = [
+        'typo',
+        'words',
+        'proximity',
+        'attribute',
+        'wordsPosition',
+        'exactness',
+    ];
+
+    const DEFAULT_SEARCHABLE_ATTRIBUTES = ['*'];
+    const DEFAULT_DISPLAYED_ATTRIBUTES = ['*'];
+
+    public function testGetDefaultSettings(): void
+    {
+        $primaryKey = 'ObjectID';
+        $settingA = $this->client
+            ->createIndex('indexA')
+            ->getSettings();
+        $settingB = $this->client
+            ->createIndex(
+                'indexB',
+                ['primaryKey' => $primaryKey]
+            )->getSettings();
+
+        $this->assertEquals(self::DEFAULT_RANKING_RULES, $settingA['rankingRules']);
+        $this->assertNull($settingA['distinctAttribute']);
+        $this->assertIsArray($settingA['searchableAttributes']);
+        $this->assertEquals(self::DEFAULT_SEARCHABLE_ATTRIBUTES, $settingA['searchableAttributes']);
+        $this->assertIsArray($settingA['displayedAttributes']);
+        $this->assertEquals(self::DEFAULT_DISPLAYED_ATTRIBUTES, $settingA['displayedAttributes']);
+        $this->assertIsArray($settingA['stopWords']);
+        $this->assertEmpty($settingA['stopWords']);
+        $this->assertIsArray($settingA['synonyms']);
+        $this->assertEmpty($settingA['synonyms']);
+
+        $this->assertEquals(self::DEFAULT_RANKING_RULES, $settingB['rankingRules']);
+        $this->assertNull($settingB['distinctAttribute']);
+        $this->assertEquals(self::DEFAULT_SEARCHABLE_ATTRIBUTES, $settingB['searchableAttributes']);
+        $this->assertEquals(self::DEFAULT_DISPLAYED_ATTRIBUTES, $settingB['displayedAttributes']);
+        $this->assertIsArray($settingB['stopWords']);
+        $this->assertEmpty($settingB['stopWords']);
+        $this->assertIsArray($settingB['synonyms']);
+        $this->assertEmpty($settingB['synonyms']);
+    }
+
+    public function testUpdateSettings(): void
+    {
+        $index = $this->client->createIndex('index');
+        $promise = $index->updateSettings([
+            'distinctAttribute' => 'title',
+            'rankingRules' => ['asc(title)', 'typo'],
+            'stopWords' => ['the'],
+        ]);
+        $this->assertIsValidPromise($promise);
+        $index->waitForPendingUpdate($promise['updateId']);
+
+        $settings = $index->getSettings();
+
+        $this->assertEquals(['asc(title)', 'typo'], $settings['rankingRules']);
+        $this->assertEquals('title', $settings['distinctAttribute']);
+        $this->assertIsArray($settings['searchableAttributes']);
+        $this->assertEquals(self::DEFAULT_SEARCHABLE_ATTRIBUTES, $settings['searchableAttributes']);
+        $this->assertIsArray($settings['displayedAttributes']);
+        $this->assertEquals(self::DEFAULT_DISPLAYED_ATTRIBUTES, $settings['displayedAttributes']);
+        $this->assertEquals(['the'], $settings['stopWords']);
+        $this->assertIsArray($settings['synonyms']);
+        $this->assertEmpty($settings['synonyms']);
+    }
+
+    public function testUpdateSettingsWithoutOverwritingThem(): void
+    {
+        $index = $this->client->createIndex('index');
+        $promise = $index->updateSettings([
+            'distinctAttribute' => 'title',
+            'rankingRules' => ['asc(title)', 'typo'],
+            'stopWords' => ['the'],
+        ]);
+
+        $this->assertIsValidPromise($promise);
+        $index->waitForPendingUpdate($promise['updateId']);
+
+        $promise = $index->updateSettings([
+            'searchableAttributes' => ['title'],
+        ]);
+
+        $this->assertIsValidPromise($promise);
+        $index->waitForPendingUpdate($promise['updateId']);
+
+        $settings = $index->getSettings();
+
+        $this->assertEquals(['asc(title)', 'typo'], $settings['rankingRules']);
+        $this->assertEquals('title', $settings['distinctAttribute']);
+        $this->assertEquals(['title'], $settings['searchableAttributes']);
+        $this->assertIsArray($settings['displayedAttributes']);
+        $this->assertEquals(self::DEFAULT_SEARCHABLE_ATTRIBUTES, $settings['displayedAttributes']);
+        $this->assertEquals(['the'], $settings['stopWords']);
+        $this->assertIsArray($settings['synonyms']);
+        $this->assertEmpty($settings['synonyms']);
+    }
+
+    public function testResetSettings(): void
+    {
+        $index = $this->client->createIndex('index');
+        $promise = $index->updateSettings([
+            'distinctAttribute' => 'title',
+            'rankingRules' => ['asc(title)', 'typo'],
+            'stopWords' => ['the'],
+        ]);
+        $this->assertIsValidPromise($promise);
+        $index->waitForPendingUpdate($promise['updateId']);
+
+        $promise = $index->resetSettings();
+
+        $this->assertIsValidPromise($promise);
+        $index->waitForPendingUpdate($promise['updateId']);
+
+        $settings = $index->getSettings();
+
+        $this->assertEquals(self::DEFAULT_RANKING_RULES, $settings['rankingRules']);
+        $this->assertNull($settings['distinctAttribute']);
+        $this->assertIsArray($settings['searchableAttributes']);
+        $this->assertEquals(self::DEFAULT_SEARCHABLE_ATTRIBUTES, $settings['searchableAttributes']);
+        $this->assertIsArray($settings['displayedAttributes']);
+        $this->assertEquals(self::DEFAULT_SEARCHABLE_ATTRIBUTES, $settings['displayedAttributes']);
+        $this->assertIsArray($settings['stopWords']);
+        $this->assertEmpty($settings['stopWords']);
+        $this->assertIsArray($settings['synonyms']);
+        $this->assertEmpty($settings['synonyms']);
+    }
+}

+ 56 - 0
vendor/meilisearch/meilisearch-php/tests/Settings/StopWordsTest.php

@@ -0,0 +1,56 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Settings;
+
+use Tests\TestCase;
+
+final class StopWordsTest extends TestCase
+{
+    private $index;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->index = $this->client->createIndex('index');
+    }
+
+    public function testGetDefaultStopWords(): void
+    {
+        $response = $this->index->getStopWords();
+
+        $this->assertIsArray($response);
+        $this->assertEmpty($response);
+    }
+
+    public function testUpdateStopWords(): void
+    {
+        $newStopWords = ['the'];
+        $promise = $this->index->updateStopWords($newStopWords);
+
+        $this->assertIsValidPromise($promise);
+
+        $this->index->waitForPendingUpdate($promise['updateId']);
+        $stopWords = $this->index->getStopWords();
+
+        $this->assertIsArray($stopWords);
+        $this->assertEquals($newStopWords, $stopWords);
+    }
+
+    public function testResetStopWords(): void
+    {
+        $promise = $this->index->updateStopWords(['the']);
+        $this->index->waitForPendingUpdate($promise['updateId']);
+
+        $promise = $this->index->resetStopWords();
+
+        $this->assertIsValidPromise($promise);
+        $this->index->waitForPendingUpdate($promise['updateId']);
+
+        $topWords = $this->index->getStopWords();
+
+        $this->assertIsArray($topWords);
+        $this->assertEmpty($topWords);
+    }
+}

+ 59 - 0
vendor/meilisearch/meilisearch-php/tests/Settings/SynonymsTest.php

@@ -0,0 +1,59 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests\Settings;
+
+use Tests\TestCase;
+
+final class SynonymsTest extends TestCase
+{
+    private $index;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->index = $this->client->createIndex('index');
+    }
+
+    public function testGetDefaultSynonyms(): void
+    {
+        $response = $this->index->getSynonyms();
+
+        $this->assertIsArray($response);
+        $this->assertEmpty($response);
+    }
+
+    public function testUpdateSynonyms(): void
+    {
+        $newSynonyms = [
+            'hp' => ['harry potter'],
+        ];
+        $promise = $this->index->updateSynonyms($newSynonyms);
+
+        $this->assertIsValidPromise($promise);
+
+        $this->index->waitForPendingUpdate($promise['updateId']);
+        $synonyms = $this->index->getSynonyms();
+
+        $this->assertIsArray($synonyms);
+        $this->assertEquals($newSynonyms, $synonyms);
+    }
+
+    public function testResetSynonyms(): void
+    {
+        $promise = $this->index->updateSynonyms([
+            'hp' => ['harry potter'],
+        ]);
+        $this->index->waitForPendingUpdate($promise['updateId']);
+        $promise = $this->index->resetSynonyms();
+
+        $this->assertIsValidPromise($promise);
+
+        $this->index->waitForPendingUpdate($promise['updateId']);
+        $synonyms = $this->index->getSynonyms();
+
+        $this->assertIsArray($synonyms);
+        $this->assertEmpty($synonyms);
+    }
+}

+ 47 - 0
vendor/meilisearch/meilisearch-php/tests/TestCase.php

@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Tests;
+
+use MeiliSearch\Client;
+use PHPUnit\Framework\TestCase as BaseTestCase;
+
+abstract class TestCase extends BaseTestCase
+{
+    protected const DOCUMENTS = [
+        ['id' => 123, 'title' => 'Pride and Prejudice', 'comment' => 'A great book', 'genre' => 'romance'],
+        ['id' => 456, 'title' => 'Le Petit Prince', 'comment' => 'A french book', 'genre' => 'adventure'],
+        ['id' => 2, 'title' => 'Le Rouge et le Noir', 'comment' => 'Another french book', 'genre' => 'romance'],
+        ['id' => 1, 'title' => 'Alice In Wonderland', 'comment' => 'A weird book', 'genre' => 'fantasy'],
+        ['id' => 1344, 'title' => 'The Hobbit', 'comment' => 'An awesome book', 'genre' => 'romance'],
+        ['id' => 4, 'title' => 'Harry Potter and the Half-Blood Prince', 'comment' => 'The best book', 'genre' => 'fantasy'],
+        ['id' => 42, 'title' => 'The Hitchhiker\'s Guide to the Galaxy'],
+    ];
+
+    protected const HOST = 'http://localhost:7700';
+
+    protected const DEFAULT_KEY = 'masterKey';
+
+    /**
+     * @var Client
+     */
+    protected $client;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->client = new Client(self::HOST, self::DEFAULT_KEY);
+    }
+
+    protected function tearDown(): void
+    {
+        $this->client->deleteAllIndexes();
+    }
+
+    public function assertIsValidPromise(array $promise): void
+    {
+        $this->assertIsArray($promise);
+        $this->assertArrayHasKey('updateId', $promise);
+    }
+}

+ 24 - 0
vendor/php-http/client-common/.php_cs.dist

@@ -0,0 +1,24 @@
+<?php
+
+/*
+ * Configuration for fabpot/php-cs-fixer
+ *
+ * @link https://github.com/FriendsOfPHP/PHP-CS-Fixer
+ */
+
+$finder = PhpCsFixer\Finder::create()
+    ->in('src')
+    ->in('spec')
+;
+return PhpCsFixer\Config::create()
+    ->setRules([
+        '@PSR2' => true,
+        '@Symfony' => true,
+        'array_syntax' => [
+            'syntax' => 'short',
+        ],
+        'no_empty_phpdoc' => true,
+        'phpdoc_to_comment' => false,
+        'single_line_throw' => false,
+    ])
+    ->setFinder($finder);

+ 276 - 0
vendor/php-http/client-common/CHANGELOG.md

@@ -0,0 +1,276 @@
+# Change Log
+
+## 2.5.0 - 2021-11-26
+
+### Added
+
+- Support for Symfony 6
+- Support for PHP 8.1
+
+### Changed
+
+- Dropped support for Symfony 2 and 3 - please keep using version 2.4.0 of this library if you can't update Symfony.
+
+## 2.4.0 - 2021-07-05
+
+### Added
+
+- `strict` option to `RedirectPlugin` to allow preserving the request method on redirections with status 300, 301 and 302.
+
+## 2.3.0 - 2020-07-21
+
+### Fixed
+
+- HttpMethodsClient with PSR RequestFactory
+- Bug in the cookie plugin with empty cookies
+- Bug when parsing null-valued date headers
+
+### Changed
+
+- Deprecation when constructing a HttpMethodsClient with PSR RequestFactory but without a StreamFactory
+
+## 2.2.1 - 2020-07-13
+
+### Fixed
+
+- Support for PHP 8
+- Plugin callable phpdoc
+
+## 2.2.0 - 2020-07-02
+
+### Added
+
+- Plugin client builder for making a `PluginClient`
+- Support for the PSR-17 request factory in `HttpMethodsClient`
+
+### Changed
+
+- Restored support for `symfony/options-resolver: ^2.6`
+- Consistent implementation of union type checking
+
+### Fixed
+
+- Memory leak when using the `PluginClient` with plugins
+
+## 2.1.0 - 2019-11-18
+
+### Added
+
+- Support Symfony 5
+
+## 2.0.0 - 2019-02-03
+
+### Changed
+
+- HttpClientRouter now throws a HttpClientNoMatchException instead of a RequestException if it can not find a client for the request.
+- RetryPlugin will only retry exceptions when there is no response, or a response in the 5xx HTTP code range.
+- RetryPlugin also retries when no exception is thrown if the responses has HTTP code in the 5xx range.
+  The callbacks for exception handling have been renamed and callbacks for response handling have been added.
+- Abstract method `HttpClientPool::chooseHttpClient()` has now an explicit return type (`Http\Client\Common\HttpClientPoolItem`)
+- Interface method `Plugin::handleRequest(...)` has now an explicit return type (`Http\Promise\Promise`)
+- Made  classes final that are not intended to be extended.
+- Added interfaces for BatchClient, HttpClientRouter and HttpMethodsClient.
+  (These interfaces use the `Interface` suffix to avoid name collisions.)
+- Added an interface for HttpClientPool and moved the abstract class to the HttpClientPool sub namespace.
+- AddPathPlugin: Do not add the prefix if the URL already has the same prefix.
+- All exceptions in `Http\Client\Common\Exception` are final.
+
+### Removed
+
+- Deprecated option `debug_plugins` has been removed from `PluginClient`
+- Deprecated options `decider` and `delay` have been removed from `RetryPlugin`, use `exception_decider` and `exception_delay` instead.
+
+## 1.11.0 - 2021-07-11
+
+### Changed
+
+- Backported from version 2: AddPathPlugin: Do not add the prefix if the URL already has the same prefix.
+
+## 1.10.0 - 2019-11-18
+
+### Added
+
+- Support for Symfony 5
+
+## 1.9.1 - 2019-02-02
+
+### Added
+
+- Updated type hints in doc blocks.
+
+## 1.9.0 - 2019-01-03
+
+### Added
+
+- Support for PSR-18 clients
+- Added traits `VersionBridgePlugin` and `VersionBridgeClient` to help plugins and clients to support both
+  1.x and 2.x version of `php-http/client-common` and `php-http/httplug`.
+
+### Changed
+
+- RetryPlugin: Renamed the configuration options for the exception retry callback from `decider` to `exception_decider`
+  and `delay` to `exception_delay`. The old names still work but are deprecated.
+
+## 1.8.2 - 2018-12-14
+
+### Changed
+
+- When multiple cookies exist, a single header with all cookies is sent as per RFC 6265 Section 5.4
+- AddPathPlugin will now trim of ending slashes in paths
+
+## 1.8.1 - 2018-10-09
+
+### Fixed
+
+- Reverted change to RetryPlugin so it again waits when retrying to avoid "can only throw objects" error.
+
+## 1.8.0 - 2018-09-21
+
+### Added
+
+ - Add an option on ErrorPlugin to only throw exception on response with 5XX status code.
+
+### Changed
+
+- AddPathPlugin no longer add prefix multiple times if a request is restarted - it now only adds the prefix if that request chain has not yet passed through the AddPathPlugin
+- RetryPlugin no longer wait for retried requests and use a deferred promise instead
+
+### Fixed
+
+- Decoder plugin will now remove header when there is no more encoding, instead of setting to an empty array
+
+## 1.7.0 - 2017-11-30
+
+### Added
+
+- Symfony 4 support
+
+### Changed
+
+- Strict comparison in DecoderPlugin
+
+## 1.6.0 - 2017-10-16
+
+### Added
+
+- Add HttpClientPool client to leverage load balancing and fallback mechanism [see the documentation](http://docs.php-http.org/en/latest/components/client-common.html) for more details.
+- `PluginClientFactory` to create `PluginClient` instances.
+- Added new option 'delay' for `RetryPlugin`.
+- Added new option 'decider' for `RetryPlugin`.
+- Supports more cookie date formats in the Cookie Plugin
+
+### Changed
+
+- The `RetryPlugin` does now wait between retries. To disable/change this feature you must write something like:
+
+```php
+$plugin = new RetryPlugin(['delay' => function(RequestInterface $request, Exception $e, $retries) {
+  return 0;
+});
+```
+
+### Deprecated
+
+- The `debug_plugins` option for `PluginClient` is deprecated and will be removed in 2.0. Use the decorator design pattern instead like in [ProfilePlugin](https://github.com/php-http/HttplugBundle/blob/de33f9c14252f22093a5ec7d84f17535ab31a384/Collector/ProfilePlugin.php).
+
+## 1.5.0 - 2017-03-30
+
+### Added
+
+- `QueryDefaultsPlugin` to add default query parameters.
+
+## 1.4.2 - 2017-03-18
+
+### Deprecated
+
+- `DecoderPlugin` does not longer claim to support `compress` content encoding
+
+### Fixed
+
+- `CookiePlugin` allows main domain cookies to be sent/stored for subdomains
+- `DecoderPlugin` uses the right `FilteredStream` to handle `deflate` content encoding
+
+
+## 1.4.1 - 2017-02-20
+
+### Fixed
+
+- Cast return value of `StreamInterface::getSize` to string in `ContentLengthPlugin`
+
+
+## 1.4.0 - 2016-11-04
+
+### Added
+
+- Add Path plugin
+- Base URI plugin that combines Add Host and Add Path plugins
+
+
+## 1.3.0 - 2016-10-16
+
+### Changed
+
+- Fix Emulated Trait to use Http based promise which respect the HttpAsyncClient interface
+- Require Httplug 1.1 where we use HTTP specific promises.
+- RedirectPlugin: use the full URL instead of the URI to properly keep track of redirects
+- Add AddPathPlugin for API URLs with base path
+- Add BaseUriPlugin that combines AddHostPlugin and AddPathPlugin
+
+
+## 1.2.1 - 2016-07-26
+
+### Changed
+
+- AddHostPlugin also sets the port if specified
+
+
+## 1.2.0 - 2016-07-14
+
+### Added
+
+- Suggest separate plugins in composer.json
+- Introduced `debug_plugins` option for `PluginClient`
+
+
+## 1.1.0 - 2016-05-04
+
+### Added
+
+- Add a flexible http client providing both contract, and only emulating what's necessary
+- HTTP Client Router: route requests to underlying clients
+- Plugin client and core plugins moved here from `php-http/plugins`
+
+### Deprecated
+
+- Extending client classes, they will be made final in version 2.0
+
+
+## 1.0.0 - 2016-01-27
+
+### Changed
+
+- Remove useless interface in BatchException
+
+
+## 0.2.0 - 2016-01-12
+
+### Changed
+
+- Updated package files
+- Updated HTTPlug to RC1
+
+
+## 0.1.1 - 2015-12-26
+
+### Added
+
+- Emulated clients
+
+
+## 0.1.0 - 2015-12-25
+
+### Added
+
+- Batch client from utils
+- Methods client from utils
+- Emulators and decorators from client-tools

+ 19 - 0
vendor/php-http/client-common/LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2015-2016 PHP HTTP Team <team@php-http.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 55 - 0
vendor/php-http/client-common/README.md

@@ -0,0 +1,55 @@
+# HTTP Client Common
+
+[![Latest Version](https://img.shields.io/github/release/php-http/client-common.svg?style=flat-square)](https://github.com/php-http/client-common/releases)
+[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
+[![Build Status](https://img.shields.io/travis/php-http/client-common/master.svg?style=flat-square)](https://travis-ci.org/php-http/client-common)
+[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/php-http/client-common.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/client-common)
+[![Quality Score](https://img.shields.io/scrutinizer/g/php-http/client-common.svg?style=flat-square)](https://scrutinizer-ci.com/g/php-http/client-common)
+[![Total Downloads](https://img.shields.io/packagist/dt/php-http/client-common.svg?style=flat-square)](https://packagist.org/packages/php-http/client-common)
+
+**Common HTTP Client implementations and tools for HTTPlug.**
+
+
+## Install
+
+Via Composer
+
+``` bash
+$ composer require php-http/client-common
+```
+
+
+## Usage
+
+This package provides common tools for HTTP Clients:
+
+- BatchClient to handle sending requests in parallel
+- A convenience client with HTTP method names as class methods
+- Emulator, decorator layers for sync/async clients
+
+
+## Documentation
+
+Please see the [official documentation](http://docs.php-http.org/en/latest/components/client-common.html).
+
+
+## Testing
+
+``` bash
+$ composer test
+```
+
+
+## Contributing
+
+Please see our [contributing guide](http://docs.php-http.org/en/latest/development/contributing.html).
+
+
+## Security
+
+If you discover any security related issues, please contact us at [security@php-http.org](mailto:security@php-http.org).
+
+
+## License
+
+The MIT License (MIT). Please see [License File](LICENSE) for more information.

+ 67 - 0
vendor/php-http/client-common/composer.json

@@ -0,0 +1,67 @@
+{
+    "name": "php-http/client-common",
+    "description": "Common HTTP Client implementations and tools for HTTPlug",
+    "license": "MIT",
+    "keywords": ["http", "client", "httplug", "common"],
+    "homepage": "http://httplug.io",
+    "authors": [
+        {
+            "name": "Márk Sági-Kazár",
+            "email": "mark.sagikazar@gmail.com"
+        }
+    ],
+    "require": {
+        "php": "^7.1 || ^8.0",
+        "php-http/httplug": "^2.0",
+        "php-http/message": "^1.6",
+        "php-http/message-factory": "^1.0",
+        "psr/http-client": "^1.0",
+        "psr/http-factory": "^1.0",
+        "psr/http-message": "^1.0",
+        "symfony/options-resolver": "~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0 || ^6.0",
+        "symfony/polyfill-php80": "^1.17"
+    },
+    "require-dev": {
+        "doctrine/instantiator": "^1.1",
+        "guzzlehttp/psr7": "^1.4",
+        "nyholm/psr7": "^1.2",
+        "phpspec/phpspec": "^5.1 || ^6.3 || ^7.1",
+        "phpspec/prophecy": "^1.10.2",
+        "phpunit/phpunit": "^7.5.15 || ^8.5 || ^9.3"
+    },
+    "suggest": {
+        "ext-json": "To detect JSON responses with the ContentTypePlugin",
+        "ext-libxml": "To detect XML responses with the ContentTypePlugin",
+        "php-http/logger-plugin": "PSR-3 Logger plugin",
+        "php-http/cache-plugin": "PSR-6 Cache plugin",
+        "php-http/stopwatch-plugin": "Symfony Stopwatch plugin"
+    },
+    "autoload": {
+        "psr-4": {
+            "Http\\Client\\Common\\": "src/"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "spec\\Http\\Client\\Common\\": "spec/"
+        }
+    },
+    "scripts": {
+        "test": [
+            "vendor/bin/phpspec run",
+            "vendor/bin/phpunit"
+        ],
+        "test-ci": [
+            "vendor/bin/phpspec run -c phpspec.ci.yml",
+            "vendor/bin/phpunit"
+        ]
+    },
+    "config": {
+        "sort-packages": true
+    },
+    "extra": {
+        "branch-alias": {
+            "dev-master": "2.3.x-dev"
+        }
+    }
+}

+ 42 - 0
vendor/php-http/client-common/src/BatchClient.php

@@ -0,0 +1,42 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Http\Client\Common;
+
+use Http\Client\Common\Exception\BatchException;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Client\ClientInterface;
+
+final class BatchClient implements BatchClientInterface
+{
+    /**
+     * @var ClientInterface
+     */
+    private $client;
+
+    public function __construct(ClientInterface $client)
+    {
+        $this->client = $client;
+    }
+
+    public function sendRequests(array $requests): BatchResult
+    {
+        $batchResult = new BatchResult();
+
+        foreach ($requests as $request) {
+            try {
+                $response = $this->client->sendRequest($request);
+                $batchResult = $batchResult->addResponse($request, $response);
+            } catch (ClientExceptionInterface $e) {
+                $batchResult = $batchResult->addException($request, $e);
+            }
+        }
+
+        if ($batchResult->hasExceptions()) {
+            throw new BatchException($batchResult);
+        }
+
+        return $batchResult;
+    }
+}

+ 34 - 0
vendor/php-http/client-common/src/BatchClientInterface.php

@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Http\Client\Common;
+
+use Http\Client\Common\Exception\BatchException;
+use Psr\Http\Message\RequestInterface;
+
+/**
+ * BatchClient allow to sends multiple request and retrieve a Batch Result.
+ *
+ * This implementation simply loops over the requests and uses sendRequest with each of them.
+ *
+ * @author Joel Wurtz <jwurtz@jolicode.com>
+ */
+interface BatchClientInterface
+{
+    /**
+     * Send several requests.
+     *
+     * You may not assume that the requests are executed in a particular order. If the order matters
+     * for your application, use sendRequest sequentially.
+     *
+     * @param RequestInterface[] $requests The requests to send
+     *
+     * @return BatchResult Containing one result per request
+     *
+     * @throws BatchException If one or more requests fails. The exception gives access to the
+     *                        BatchResult with a map of request to result for success, request to
+     *                        exception for failures
+     */
+    public function sendRequests(array $requests): BatchResult;
+}

+ 157 - 0
vendor/php-http/client-common/src/BatchResult.php

@@ -0,0 +1,157 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Http\Client\Common;
+
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Responses and exceptions returned from parallel request execution.
+ *
+ * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
+ */
+final class BatchResult
+{
+    /**
+     * @var \SplObjectStorage<RequestInterface, ResponseInterface>
+     */
+    private $responses;
+
+    /**
+     * @var \SplObjectStorage<RequestInterface, ClientExceptionInterface>
+     */
+    private $exceptions;
+
+    public function __construct()
+    {
+        $this->responses = new \SplObjectStorage();
+        $this->exceptions = new \SplObjectStorage();
+    }
+
+    /**
+     * Checks if there are any successful responses at all.
+     */
+    public function hasResponses(): bool
+    {
+        return $this->responses->count() > 0;
+    }
+
+    /**
+     * Returns all successful responses.
+     *
+     * @return ResponseInterface[]
+     */
+    public function getResponses(): array
+    {
+        $responses = [];
+
+        foreach ($this->responses as $request) {
+            $responses[] = $this->responses[$request];
+        }
+
+        return $responses;
+    }
+
+    /**
+     * Checks if there is a successful response for a request.
+     */
+    public function isSuccessful(RequestInterface $request): bool
+    {
+        return $this->responses->contains($request);
+    }
+
+    /**
+     * Returns the response for a successful request.
+     *
+     * @throws \UnexpectedValueException If request was not part of the batch or failed
+     */
+    public function getResponseFor(RequestInterface $request): ResponseInterface
+    {
+        try {
+            return $this->responses[$request];
+        } catch (\UnexpectedValueException $e) {
+            throw new \UnexpectedValueException('Request not found', $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Adds a response in an immutable way.
+     *
+     * @return BatchResult the new BatchResult with this request-response pair added to it
+     */
+    public function addResponse(RequestInterface $request, ResponseInterface $response): self
+    {
+        $new = clone $this;
+        $new->responses->attach($request, $response);
+
+        return $new;
+    }
+
+    /**
+     * Checks if there are any unsuccessful requests at all.
+     */
+    public function hasExceptions(): bool
+    {
+        return $this->exceptions->count() > 0;
+    }
+
+    /**
+     * Returns all exceptions for the unsuccessful requests.
+     *
+     * @return ClientExceptionInterface[]
+     */
+    public function getExceptions(): array
+    {
+        $exceptions = [];
+
+        foreach ($this->exceptions as $request) {
+            $exceptions[] = $this->exceptions[$request];
+        }
+
+        return $exceptions;
+    }
+
+    /**
+     * Checks if there is an exception for a request, meaning the request failed.
+     */
+    public function isFailed(RequestInterface $request): bool
+    {
+        return $this->exceptions->contains($request);
+    }
+
+    /**
+     * Returns the exception for a failed request.
+     *
+     * @throws \UnexpectedValueException If request was not part of the batch or was successful
+     */
+    public function getExceptionFor(RequestInterface $request): ClientExceptionInterface
+    {
+        try {
+            return $this->exceptions[$request];
+        } catch (\UnexpectedValueException $e) {
+            throw new \UnexpectedValueException('Request not found', $e->getCode(), $e);
+        }
+    }
+
+    /**
+     * Adds an exception in an immutable way.
+     *
+     * @return BatchResult the new BatchResult with this request-exception pair added to it
+     */
+    public function addException(RequestInterface $request, ClientExceptionInterface $exception): self
+    {
+        $new = clone $this;
+        $new->exceptions->attach($request, $exception);
+
+        return $new;
+    }
+
+    public function __clone()
+    {
+        $this->responses = clone $this->responses;
+        $this->exceptions = clone $this->exceptions;
+    }
+}

+ 152 - 0
vendor/php-http/client-common/src/Deferred.php

@@ -0,0 +1,152 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Http\Client\Common;
+
+use Http\Promise\Promise;
+use Psr\Http\Client\ClientExceptionInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * A deferred allow to return a promise which has not been resolved yet.
+ */
+final class Deferred implements Promise
+{
+    /**
+     * @var ResponseInterface|null
+     */
+    private $value;
+
+    /**
+     * @var ClientExceptionInterface|null
+     */
+    private $failure;
+
+    /**
+     * @var string
+     */
+    private $state;
+
+    /**
+     * @var callable
+     */
+    private $waitCallback;
+
+    /**
+     * @var callable[]
+     */
+    private $onFulfilledCallbacks;
+
+    /**
+     * @var callable[]
+     */
+    private $onRejectedCallbacks;
+
+    public function __construct(callable $waitCallback)
+    {
+        $this->waitCallback = $waitCallback;
+        $this->state = Promise::PENDING;
+        $this->onFulfilledCallbacks = [];
+        $this->onRejectedCallbacks = [];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function then(callable $onFulfilled = null, callable $onRejected = null): Promise
+    {
+        $deferred = new self($this->waitCallback);
+
+        $this->onFulfilledCallbacks[] = function (ResponseInterface $response) use ($onFulfilled, $deferred) {
+            try {
+                if (null !== $onFulfilled) {
+                    $response = $onFulfilled($response);
+                }
+                $deferred->resolve($response);
+            } catch (ClientExceptionInterface $exception) {
+                $deferred->reject($exception);
+            }
+        };
+
+        $this->onRejectedCallbacks[] = function (ClientExceptionInterface $exception) use ($onRejected, $deferred) {
+            try {
+                if (null !== $onRejected) {
+                    $response = $onRejected($exception);
+                    $deferred->resolve($response);
+
+                    return;
+                }
+                $deferred->reject($exception);
+            } catch (ClientExceptionInterface $newException) {
+                $deferred->reject($newException);
+            }
+        };
+
+        return $deferred;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getState(): string
+    {
+        return $this->state;
+    }
+
+    /**
+     * Resolve this deferred with a Response.
+     */
+    public function resolve(ResponseInterface $response): void
+    {
+        if (Promise::PENDING !== $this->state) {
+            return;
+        }
+
+        $this->value = $response;
+        $this->state = Promise::FULFILLED;
+
+        foreach ($this->onFulfilledCallbacks as $onFulfilledCallback) {
+            $onFulfilledCallback($response);
+        }
+    }
+
+    /**
+     * Reject this deferred with an Exception.
+     */
+    public function reject(ClientExceptionInterface $exception): void
+    {
+        if (Promise::PENDING !== $this->state) {
+            return;
+        }
+
+        $this->failure = $exception;
+        $this->state = Promise::REJECTED;
+
+        foreach ($this->onRejectedCallbacks as $onRejectedCallback) {
+            $onRejectedCallback($exception);
+        }
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function wait($unwrap = true)
+    {
+        if (Promise::PENDING === $this->state) {
+            $callback = $this->waitCallback;
+            $callback();
+        }
+
+        if (!$unwrap) {
+            return null;
+        }
+
+        if (Promise::FULFILLED === $this->state) {
+            return $this->value;
+        }
+
+        /** @var ClientExceptionInterface */
+        throw $this->failure;
+    }
+}

+ 25 - 0
vendor/php-http/client-common/src/EmulatedHttpAsyncClient.php

@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Http\Client\Common;
+
+use Http\Client\HttpAsyncClient;
+use Http\Client\HttpClient;
+use Psr\Http\Client\ClientInterface;
+
+/**
+ * Emulates an async HTTP client with the help of a synchronous client.
+ *
+ * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
+ */
+final class EmulatedHttpAsyncClient implements HttpClient, HttpAsyncClient
+{
+    use HttpAsyncClientEmulator;
+    use HttpClientDecorator;
+
+    public function __construct(ClientInterface $httpClient)
+    {
+        $this->httpClient = $httpClient;
+    }
+}

+ 24 - 0
vendor/php-http/client-common/src/EmulatedHttpClient.php

@@ -0,0 +1,24 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Http\Client\Common;
+
+use Http\Client\HttpAsyncClient;
+use Http\Client\HttpClient;
+
+/**
+ * Emulates a synchronous HTTP client with the help of an asynchronous client.
+ *
+ * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
+ */
+final class EmulatedHttpClient implements HttpClient, HttpAsyncClient
+{
+    use HttpAsyncClientDecorator;
+    use HttpClientEmulator;
+
+    public function __construct(HttpAsyncClient $httpAsyncClient)
+    {
+        $this->httpAsyncClient = $httpAsyncClient;
+    }
+}

+ 37 - 0
vendor/php-http/client-common/src/Exception/BatchException.php

@@ -0,0 +1,37 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Http\Client\Common\Exception;
+
+use Http\Client\Common\BatchResult;
+use Http\Client\Exception\TransferException;
+
+/**
+ * This exception is thrown when HttpClient::sendRequests led to at least one failure.
+ *
+ * It gives access to a BatchResult with the request-exception and request-response pairs.
+ *
+ * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
+ */
+final class BatchException extends TransferException
+{
+    /**
+     * @var BatchResult
+     */
+    private $result;
+
+    public function __construct(BatchResult $result)
+    {
+        $this->result = $result;
+        parent::__construct();
+    }
+
+    /**
+     * Returns the BatchResult that contains all responses and exceptions.
+     */
+    public function getResult(): BatchResult
+    {
+        return $this->result;
+    }
+}

+ 16 - 0
vendor/php-http/client-common/src/Exception/CircularRedirectionException.php

@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Http\Client\Common\Exception;
+
+use Http\Client\Exception\HttpException;
+
+/**
+ * Thrown when circular redirection is detected.
+ *
+ * @author Joel Wurtz <joel.wurtz@gmail.com>
+ */
+final class CircularRedirectionException extends HttpException
+{
+}

+ 16 - 0
vendor/php-http/client-common/src/Exception/ClientErrorException.php

@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Http\Client\Common\Exception;
+
+use Http\Client\Exception\HttpException;
+
+/**
+ * Thrown when there is a client error (4xx).
+ *
+ * @author Joel Wurtz <joel.wurtz@gmail.com>
+ */
+final class ClientErrorException extends HttpException
+{
+}

+ 33 - 0
vendor/php-http/client-common/src/Exception/HttpClientNoMatchException.php

@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Http\Client\Common\Exception;
+
+use Http\Client\Exception\TransferException;
+use Psr\Http\Message\RequestInterface;
+
+/**
+ * Thrown when a http client match in the HTTPClientRouter.
+ *
+ * @author Tobias Nyholm <tobias.nyholm@gmail.com>
+ */
+final class HttpClientNoMatchException extends TransferException
+{
+    /**
+     * @var RequestInterface
+     */
+    private $request;
+
+    public function __construct(string $message, RequestInterface $request, \Exception $previous = null)
+    {
+        $this->request = $request;
+
+        parent::__construct($message, 0, $previous);
+    }
+
+    public function getRequest(): RequestInterface
+    {
+        return $this->request;
+    }
+}

+ 16 - 0
vendor/php-http/client-common/src/Exception/HttpClientNotFoundException.php

@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Http\Client\Common\Exception;
+
+use Http\Client\Exception\TransferException;
+
+/**
+ * Thrown when a http client cannot be chosen in a pool.
+ *
+ * @author Joel Wurtz <joel.wurtz@gmail.com>
+ */
+final class HttpClientNotFoundException extends TransferException
+{
+}

+ 16 - 0
vendor/php-http/client-common/src/Exception/LoopException.php

@@ -0,0 +1,16 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Http\Client\Common\Exception;
+
+use Http\Client\Exception\RequestException;
+
+/**
+ * Thrown when the Plugin Client detects an endless loop.
+ *
+ * @author Joel Wurtz <joel.wurtz@gmail.com>
+ */
+final class LoopException extends RequestException
+{
+}

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff