Skip to content

Commit 23e8f53

Browse files
committed
Improve paginated response
Rework paginated response (close #31) Throw exception is client is not defined in Paginated response (close #30) Increase the code coverage
1 parent 61eefb6 commit 23e8f53

30 files changed

+893
-45
lines changed

.gitignore

+1-4
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,5 @@
99
### PHPUnit
1010
/.phpunit.result.cache
1111

12-
### Documentation
13-
/docs
14-
1512
### Local test
16-
/test*.php
13+
/test*.php

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased v2.x]
88

9+
### Added
10+
11+
- Documentation on paginated responses
12+
- Throw custom exception is client is missing
13+
914
### Changed
1015

16+
- Rework paginated responses
1117
- (dev) Update version and rules of PHP-CS-Fixer and PHPStan
1218

1319
## [2.1.1]

docs/BuildQuery.md

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Building a Query
2+
3+
To help create RediSearch, the library offer a builder.
4+
5+
It all start with the class `\MacFJA\RediSearch\Query\Builder`. From there you can add groups and query elements.
6+
7+
---
8+
9+
For example, for search all people named `John`, and preferably with the last name `Doe` that work as an accountant in the 10 km around Washington DC, and the resume contain text `cake` the request will be:
10+
```php
11+
use MacFJA\RediSearch\Query\Builder;
12+
use MacFJA\RediSearch\Query\Builder\GeoFacet;
13+
use MacFJA\RediSearch\Query\Builder\TextFacet;
14+
use MacFJA\RediSearch\Query\Builder\Optional;
15+
use MacFJA\RediSearch\Redis\Command\SearchCommand\GeoFilterOption;
16+
17+
$queryBuilder = new Builder();
18+
$query = $queryBuilder
19+
->addTextFacet('firstname', 'John') // people named `John`
20+
->addElement(new Optional(new TextFacet(['lastname'], 'Doe'))) // preferably with the last name `Doe`
21+
->addTextFacet('job', 'accountant') // work as an accountant
22+
->addGeoFacet('address', -77.0365427, 38.8950368, 10, GeoFilterOption::UNIT_KILOMETERS) // in the 10 km around Washington DC
23+
->addString('cake') // the resume contain text `cake`
24+
->render();
25+
26+
// @firstname:John ~@lastname:Doe @job:accountant @address:[-77.0365427 38.8950368 10 km] cake
27+
```

docs/UsingResult.md

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Using result
2+
3+
As you can set an offset and limit on `FT.SEARCH` and `FT.AGGREGATE`, they are paginated commands.
4+
The result of those commands is a page, that can contain one or more document.
5+
6+
---
7+
8+
So in this library, the result of `\MacFJA\RediSearch\Redis\Command\Search` and `\MacFJA\RediSearch\Redis\Command\Aggregate` is also a paginated object:
9+
either a `\MacFJA\RediSearch\Redis\Response\PaginatedResponse` (for Search and Aggregate) or a `\MacFJA\RediSearch\Redis\Response\CursorResponse` (for Aggregate)
10+
11+
The 2 classes are representing a page.
12+
13+
Both `\MacFJA\RediSearch\Redis\Response\PaginatedResponse` and `\MacFJA\RediSearch\Redis\Response\CursorResponse` can be read inside a `foreach` loop or with any iterator function.
14+
(As they implement the [`\Iterator` interface](https://www.php.net/manual/en/class.iterator.php))
15+
What you are iterating over, are the pages (not the documents inside the page).
16+
17+
```php
18+
// If your RediSearch search have 15 results in total, but you set the pagination to 10 per page:
19+
/** @var PaginatedResponse $results */
20+
21+
/** @var array<SearchResponseItem> $items */
22+
$items = $results->current();
23+
// $items is a list of 10 SearchResponseItem
24+
25+
$results->next(); // To be able to class `->next()`, you need to call `->setClient()` first !
26+
27+
/** @var array<SearchResponseItem> $items */
28+
$items = $results->current();
29+
// $items is a list of 5 SearchResponseItem
30+
```
31+
32+
## Get all items on all pages
33+
34+
```php
35+
/** @var \MacFJA\RediSearch\Redis\Client $client */
36+
/** @var \MacFJA\RediSearch\Redis\Command\Search $search */
37+
38+
/** @var \MacFJA\RediSearch\Redis\Response\PaginatedResponse $results */
39+
$results = $client->execute($search);
40+
$results->setClient($client);
41+
42+
$items = [];
43+
foreach ($results as $pageIndex => $pageContent) {
44+
$items = array_merge($items, $pageContent);
45+
}
46+
/** @var \MacFJA\RediSearch\Redis\Response\SearchResponseItem $item */
47+
foreach ($items as $item) {
48+
doSomething($item);
49+
}
50+
```
51+
Be careful, this will load all items in the memory.
52+
This will also call Redis multiple time if there are several pages.
53+
54+
---
55+
56+
If you need to avoid loading all items in the memory, you can use [`ResponseItemIterator`](https://github.com/MacFJA/php-redisearch-integration/blob/main/src/Iterator/ResponseItemIterator.php) from the sister project [macfja/redisearch-integration](https://github.com/MacFJA/php-redisearch-integration).
57+
This class it a custom iterator that will load in memory only one page at the time.
58+
59+
```php
60+
/** @var \MacFJA\RediSearch\Redis\Client $client */
61+
/** @var \MacFJA\RediSearch\Redis\Command\Search $search */
62+
63+
$results = $client->execute($search);
64+
$allItems = new \MacFJA\RediSearch\Integration\Iterator\ResponseItemIterator($results, $client);
65+
/** @var \MacFJA\RediSearch\Redis\Response\SearchResponseItem $item */
66+
foreach ($allItems as $item) {
67+
// Only one page exist in the memory
68+
doSomething($item);
69+
}
70+
```
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* Copyright MacFJA
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
9+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
10+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
11+
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
14+
* Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
17+
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
namespace MacFJA\RediSearch\Exception;
23+
24+
use RuntimeException;
25+
use Throwable;
26+
27+
class MissingClientException extends RuntimeException
28+
{
29+
public function __construct(Throwable $previous = null)
30+
{
31+
parent::__construct('The Redis client is missing. You need to manually set it.', 0, $previous);
32+
}
33+
}

src/Redis/Client/AmpRedisClient.php

+6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ class AmpRedisClient extends AbstractClient
3333
/** @var Redis */
3434
private $redis;
3535

36+
/**
37+
* @codeCoverageIgnore
38+
*/
3639
private function __construct(Redis $redis)
3740
{
3841
if (!static::supports($redis)) {
@@ -77,6 +80,9 @@ public function pipeline(Command ...$commands): array
7780
}, $commands);
7881
}
7982

83+
/**
84+
* @codeCoverageIgnore
85+
*/
8086
protected function doPipeline(Command ...$commands): array
8187
{
8288
return [];

src/Redis/Client/CheprasovRedisClient.php

+3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ class CheprasovRedisClient extends AbstractClient
3333
/** @var AbstractRedisClient */
3434
private $redis;
3535

36+
/**
37+
* @codeCoverageIgnore
38+
*/
3639
private function __construct(AbstractRedisClient $redis)
3740
{
3841
if (!static::supports($redis)) {

src/Redis/Client/CredisClient.php

+3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ class CredisClient extends AbstractClient
3232
/** @var Credis_Client */
3333
private $redis;
3434

35+
/**
36+
* @codeCoverageIgnore
37+
*/
3538
private function __construct(Credis_Client $redis)
3639
{
3740
if (!self::supports($redis)) {

src/Redis/Client/PredisClient.php

+3
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ class PredisClient extends AbstractClient
3434
/** @var ClientInterface */
3535
private $redis;
3636

37+
/**
38+
* @codeCoverageIgnore
39+
*/
3740
private function __construct(ClientInterface $redis)
3841
{
3942
if (!static::supports($redis)) {

src/Redis/Client/RedisentClient.php

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ class RedisentClient extends AbstractClient
3131
/** @var Redis */
3232
private $redis;
3333

34+
/**
35+
* @codeCoverageIgnore
36+
*/
3437
private function __construct(Redis $redis)
3538
{
3639
if (!static::supports($redis)) {

src/Redis/Client/Rediska/RediskaRediSearchCommand.php

+3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
use Rediska_Command_Abstract;
2727
use Rediska_Connection_Exec;
2828

29+
/**
30+
* @codeCoverageIgnore
31+
*/
2932
class RediskaRediSearchCommand extends Rediska_Command_Abstract
3033
{
3134
public function create(): Rediska_Connection_Exec

src/Redis/Client/RediskaClient.php

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ class RediskaClient extends AbstractClient
3636
/** @var Rediska */
3737
private $redis;
3838

39+
/**
40+
* @codeCoverageIgnore
41+
*/
3942
private function __construct(Rediska $redis)
4043
{
4144
if (!static::supports($redis)) {

src/Redis/Client/TinyRedisClient.php

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ class TinyRedisClient extends AbstractClient
3030
/** @var \TinyRedisClient */
3131
private $redis;
3232

33+
/**
34+
* @codeCoverageIgnore
35+
*/
3336
public function __construct(\TinyRedisClient $redis)
3437
{
3538
if (!self::supports($redis)) {

src/Redis/Response/AggregateResponseItem.php

+17-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
/**
2525
* @codeCoverageIgnore Simple value object
2626
*/
27-
class AggregateResponseItem
27+
class AggregateResponseItem implements ResponseItem
2828
{
2929
/** @var array<string,null|array<mixed>|float|int|string> */
3030
private $fields = [];
@@ -49,8 +49,24 @@ public function getFields(): array
4949
* @param null|array<mixed>|float|int|string $default
5050
*
5151
* @return null|array<mixed>|float|int|string
52+
*
53+
* @deprecated Use ::getFieldValue instead
54+
* @codeCoverageIgnore
55+
* @psalm-suppress
56+
* @SuppressWarnings
5257
*/
5358
public function getValue(string $fieldName, $default = null)
59+
{
60+
trigger_error(sprintf(
61+
'Method %s is deprecated from version 2.2.0, use %s::getFieldValue instead',
62+
__METHOD__,
63+
__CLASS__
64+
), E_USER_DEPRECATED);
65+
// @phpstan-ignore-next-line
66+
return $this->getFieldValue($fieldName, $default);
67+
}
68+
69+
public function getFieldValue(string $fieldName, $default = null)
5470
{
5571
return $this->fields[$fieldName] ?? $default;
5672
}

src/Redis/Response/ArrayResponseTrait.php

+5-2
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,12 @@ private static function getNextValue(array $notKeyedArray, string $key)
8686
*/
8787
private static function getKeys(array $notKeyedArray): array
8888
{
89-
$keys = array_filter(array_values($notKeyedArray), static function (int $key): bool {
89+
if (count($notKeyedArray) % 2 > 0) {
90+
array_pop($notKeyedArray);
91+
}
92+
$keys = array_values(array_filter(array_values($notKeyedArray), static function (int $key): bool {
9093
return 0 === $key % 2;
91-
}, ARRAY_FILTER_USE_KEY);
94+
}, ARRAY_FILTER_USE_KEY));
9295

9396
return array_map(static function ($key): string {
9497
return (string) $key;

src/Redis/Response/ClientAware.php

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* Copyright MacFJA
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
9+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
10+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
11+
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
14+
* Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
17+
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
namespace MacFJA\RediSearch\Redis\Response;
23+
24+
use MacFJA\RediSearch\Redis\Client;
25+
26+
interface ClientAware
27+
{
28+
public function setClient(Client $client): ClientAware;
29+
30+
public function getClient(): ?Client;
31+
}
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* Copyright MacFJA
7+
*
8+
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
9+
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
10+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
11+
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
14+
* Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
17+
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18+
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19+
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
*/
21+
22+
namespace MacFJA\RediSearch\Redis\Response;
23+
24+
use MacFJA\RediSearch\Redis\Client;
25+
26+
trait ClientAwareTrait
27+
{
28+
/** @var null|Client */
29+
private $client;
30+
31+
public function getClient(): ?Client
32+
{
33+
return $this->client;
34+
}
35+
36+
public function setClient(Client $client): ClientAware
37+
{
38+
$this->client = $client;
39+
40+
return $this;
41+
}
42+
}

0 commit comments

Comments
 (0)