Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add some test + doc #14

Merged
merged 2 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .phpunit.result.cache
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version":1,"defects":[],"times":{"Talleu\\RedisOm\\Tests\\Functionnal\\DataConsistency\\ArrayConsistencyTest::testArrayHash":0.11,"Talleu\\RedisOm\\Tests\\Functionnal\\DataConsistency\\ArrayConsistencyTest::testArrayJson":0.043,"Talleu\\RedisOm\\Tests\\Functionnal\\DataConsistency\\DatesConsistencyTest::testDateHash":0.038,"Talleu\\RedisOm\\Tests\\Functionnal\\DataConsistency\\DatesConsistencyTest::testDateJson":0.038,"Talleu\\RedisOm\\Tests\\Functionnal\\DataConsistency\\ObjectConsistencyTest::testBookHash":0.037,"Talleu\\RedisOm\\Tests\\Functionnal\\DataConsistency\\ObjectConsistencyTest::testBookJson":0.031,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\Mapping\\MappingPropertyTest::testPropertyNotMappedJson":0.032,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\Mapping\\MappingPropertyTest::testPropertyNotMappedHash":0.032,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\Mapping\\PrivatePropertiesTest::testPropertyNotPublic":0.051,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\Mapping\\PrivatePropertiesTest::testPropertyNotPublicJson":0.041,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\ObjectManagerTest::testPersistAndFlushHash":0.033,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\ObjectManagerTest::testPersistAndFlushJson":0.034,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\ObjectManagerTest::testFindJson":0.038,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\ObjectManagerTest::testFindHash":0.039,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\ObjectManagerTest::testRemove":0.033,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\Repository\\HashModel\\HashRepositoryTest::testFindAll":0.032,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\Repository\\HashModel\\HashRepositoryTest::testFindBy":0.033,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\Repository\\HashModel\\HashRepositoryTest::testFindByOrder":0.036,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\Repository\\HashModel\\HashRepositoryTest::testFindByMultiCriterias":0.033,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\Repository\\HashModel\\HashRepositoryTest::testFindOneBy":0.033,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\Repository\\JsonModel\\JsonRepositoryTest::testFindAll":0.095,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\Repository\\JsonModel\\JsonRepositoryTest::testFindBy":0.034,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\Repository\\JsonModel\\JsonRepositoryTest::testFindByOrder":0.056,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\Repository\\JsonModel\\JsonRepositoryTest::testFindByMultiCriterias":0.034,"Talleu\\RedisOm\\Tests\\Functionnal\\Om\\Repository\\JsonModel\\JsonRepositoryTest::testFindOneBy":0.03}}
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ with Redis.
- Doctrine-like methods and architecture
- Easy integration with existing PHP applications
- High performance and scalability with Redis
- Support for Redis JSON module
- Automatic schema generation
- Search and query capabilities

## Requirements

- PHP 8.2 or higher
- Redis 4.0 or higher
- php-redis extension
- Redis JSON and Redisearch modules (optional)
- php-redis extension (or your favorite Redis client)
- Redis JSON module (optional)
- Composer

## Installation
Expand Down Expand Up @@ -104,7 +107,9 @@ docker compose up -d

### Running tests


```console
docker compose exec php vendor/bin/phpunit tests
```
```


## Advanced mapping configuration
113 changes: 113 additions & 0 deletions docs/advanced_mapping_configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# php-redis-om, advanced mapping configuration


## Mapping object

You can customize the mapping configuration by adding parameters to you RedisOm\Entity attribute.

```php
<?php

use Talleu\RedisOm\Om\Mapping as RedisOm;
use Talleu\RedisOm\Om\RedisFormat;

#[RedisOm\Entity(
prefix: 'user_redis',
expires: 12000,
format: RedisFormat::JSON->value,
persister: new MyCustomPersister(),
converter: new MyCustomConverter(),
repository: new MyCustomRepository(),
redisClient: new MyCustomRedisClient(),
)]
class User
{}
```

Each of these parameters are optional and can be omitted. Here is a description of each parameter:

- prefix:
- The prefix to use for the keys in Redis. If not set, the class name will be used.
- Example: `user_redis`
- Default: `null`
- Type: `string`
- Note: The prefix will be concatenated with the id of the object to create the key in Redis.
- expires:
- The time in seconds before the entry expires in redis. If not set, the key will never expire.
- Example: `12000`
- Default: `null`
- Type: `int`
- Note: The key will be set to expire in the given time after the last write operation.
- format:
- The format to use to store the object in Redis. If not set, the default format will be used : `HASH`.
- Example: `RedisFormat::JSON->value` (JSON)
- Default: `RedisFormat::HASH->value` (HASH)
- Type: `string`
- Note: To use the "JSON" format, your redis server must have the Redis JSON module installed.
- persister:
- The persister to use to persist your objects in Redis. If not set, the default persister will be used.
- Example: `new MyCustomPersister()`
- Default: `null`
- Type: `PersisterInterface`
- Note: The persister must implement the `PersisterInterface` interface.
- converter:
- The converter to use to convert your objects to and from Redis. If not set, the default converter will be used.
- Example: `new MyCustomConverter()`
- Default: `null`
- Type: `ConverterInterface`
- Note: The converter must implement the `ConverterInterface` interface.
- repository:
- The repository to use to fetch your objects from Redis. If not set, the default repository will be used.
- Example: `new MyCustomRepository()`
- Default: `null`
- Type: `RepositoryInterface`
- Note: The repository must implement the `RepositoryInterface` interface.
- redisClient:
- The redis client to use to connect to your Redis server. If not set, the default redis client will be used.
- Example: `new MyCustomRedisClient()`
- Default: `null`
- Type: `RedisClientInterface`
- Note: The redis client must implement the `RedisClientInterface` interface, it could be a php-redis client
or any other client that implements the interface.


## Mapping properties
```php
<?php

use Talleu\RedisOm\Om\Mapping as RedisOm;
use Talleu\RedisOm\Om\RedisFormat;

#[RedisOm\Entity]
class User
{
#[RedisOm\Id]
#[RedisOm\Property]
public int $id;

#[RedisOm\Property(
name: 'user_name',
getter: 'obtainName',
setter: 'withName',
)]
public string $name;
}
```

Each of these parameters are optional and can be omitted. Here is a description of each parameter:

- name:
- The name of the key in Redis. If not set, the property name will be used.
- Example: `user_name`
- Default: `null`
- Type: `string`
- getter:
- The name of the getter method to use to get the value of the property **if the property is not public**. If not set, a default getter as : `getName()` will be used.
- Example: `obtainName`
- Default: `null`
- Type: `string`
- setter:
- The name of the setter method to use to set the value of the property **if the property is not public**. If not set, a default setter as : `setName()` will be used.
- Example: `withName`
- Default: `null`
- Type: `string`
4 changes: 3 additions & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.1/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheDirectory=".phpunit.cache"
executionOrder="depends,defects">
executionOrder="depends,defects"
displayDetailsOnTestsThatTriggerWarnings="true"
>
<testsuites>
<testsuite name="default">
<directory>tests</directory>
Expand Down
13 changes: 11 additions & 2 deletions src/Client/RedisClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public function scanKeys(string $prefixKey): array
$keys = [];
$iterator = null;
while($iterator !== 0) {
$scans = $this->redis->scan($iterator, sprintf('%s*', static::convertPrefix($prefixKey)));
$scans = $this->redis->scan($iterator, sprintf("%s*", static::convertPrefix($prefixKey)));
foreach($scans as $scan) {
$keys[] = $scan;
}
Expand Down Expand Up @@ -212,7 +212,15 @@ public function search(string $prefixKey, array $search, array $orderBy, ?string
if ($key > 0 && $key % 2 == 0) {

if ($format === RedisFormat::JSON->value) {
$data = json_decode($redisData[1], true);
foreach ($redisData as $data) {
if (!str_starts_with($data, '{')) {
continue;
}
$entities[] = json_decode($data, true);
break;
}

continue;
} else {
$data = [];
for ($i = 0; $i < count($redisData); $i += 2) {
Expand All @@ -223,6 +231,7 @@ public function search(string $prefixKey, array $search, array $orderBy, ?string
}

$entities[] = $data;

if (count($entities) === $numberOfResults) {
return $entities;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Console/Runner.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static function generateSchema(string $dirPath): void
// Not a valid directory absolute path, try to find the directory in the project root
$dirPath = __DIR__ . '/../../../../../' . $dirPath;
if (!is_dir($dirPath)) {
throw new \InvalidArgumentException(sprintf('Directory %s not found', $dirPath));
throw new \InvalidArgumentException(sprintf("Directory %s not found", $dirPath));
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/Om/Converters/AbstractArrayConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ abstract public function convert($data);

public function supportsConversion(string $type, mixed $data): bool
{
return $type === 'array' && $data !== null;
return ($type === 'array' || $type === 'iterable') && $data !== null;
}

abstract public function revert($data, string $type): mixed;

public function supportsReversion(string $type, mixed $value): bool
{
if (is_array($value) && array_key_exists('date', $value) && array_key_exists('timezone', $value)) {
return false;
}
// if (is_array($value) && array_key_exists('date', $value) && array_key_exists('timezone', $value)) {
// return false;
// }

return $type === 'array';
return $type === 'array' || $type === 'iterable' && $value !== null;
}
}
4 changes: 2 additions & 2 deletions src/Om/Converters/AbstractObjectConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@

abstract class AbstractObjectConverter implements ConverterInterface
{
abstract public function convert($data): \stdClass|array;
abstract public function convert($data): array;

public function supportsConversion(string $type, mixed $data): bool
{
return $data !== null && class_exists($type) && !in_array($type, AbstractDateTimeConverter::DATETYPES_NAMES);
return $data !== null && class_exists($type) && $type !== \stdClass::class && !in_array($type, AbstractDateTimeConverter::DATETYPES_NAMES);
}

abstract public function revert($data, string $type): mixed;
Expand Down
22 changes: 22 additions & 0 deletions src/Om/Converters/AbstractStandardClassConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Talleu\RedisOm\Om\Converters;

abstract class AbstractStandardClassConverter implements ConverterInterface
{
abstract public function convert($data);

public function supportsConversion(string $type, mixed $data): bool
{
return $data !== null && $type === 'stdClass';
}

abstract public function revert($data, string $type): mixed;

public function supportsReversion(string $type, mixed $value): bool
{
return $type === 'stdClass' && $value !== null;
}
}
3 changes: 2 additions & 1 deletion src/Om/Converters/HashModel/ArrayConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ public function convert($data, ?array $hashData = [], ?string $parentProperty =
continue;
}

if ($converter instanceof HashObjectConverter || $converter instanceof ArrayConverter) {
if ($converter instanceof HashObjectConverter || $converter instanceof ArrayConverter || $converter instanceof StandardClassConverter) {
$propertyKey = $parentProperty ? sprintf("%s.%s", $parentProperty, $key) : $key;
$hashData[sprintf("%s.%s.#type", $parentProperty, $key)] = $valueType;
$hashData = $converter->convert(data: $value, hashData: $hashData, parentProperty: $propertyKey, parentPropertyType: $valueType);
continue;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Om/Converters/HashModel/ConverterFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use Talleu\RedisOm\Om\Converters\AbstractConverterFactory;
use Talleu\RedisOm\Om\Converters\ConverterInterface;
use Talleu\RedisOm\Om\Converters\EnumConverter;
use Talleu\RedisOm\Om\Converters\ScalarConverter;

final class ConverterFactory extends AbstractConverterFactory
Expand All @@ -19,6 +18,7 @@ protected static function getConvertersCollection(): array
return [
new HashObjectConverter(),
new ArrayConverter(),
new StandardClassConverter(),
new ScalarConverter(),
new NullConverter(),
new DateTimeConverter(),
Expand Down
5 changes: 2 additions & 3 deletions src/Om/Converters/HashModel/HashObjectConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function convert($data, ?array $hashData = [], ?string $parentProperty =
continue;
}

if ($converter instanceof HashObjectConverter || $converter instanceof ArrayConverter) {
if ($converter instanceof HashObjectConverter || $converter instanceof ArrayConverter || $converter instanceof StandardClassConverter) {
$propertyName = $parentProperty ? sprintf("%s.%s", $parentProperty, $property->getName()) : $property->getName();
$hashData = $converter->convert(data: $value, hashData: $hashData, parentProperty: $propertyName, parentPropertyType: $valueType);
continue;
Expand Down Expand Up @@ -83,7 +83,6 @@ public function revert($data, string $type): mixed
}

$reverter = ConverterFactory::getReverter($valueType, $value);

if (!$reverter) {
continue;
}
Expand Down Expand Up @@ -121,6 +120,6 @@ private function redisArrayUnflatten(array $array)

public function supportsReversion(string $type, mixed $value): bool
{
return class_exists($type) && $value !== 'null' && !in_array($type, AbstractDateTimeConverter::DATETYPES_NAMES);
return class_exists($type) && $type !== \stdClass::class && $value !== 'null' && !in_array($type, AbstractDateTimeConverter::DATETYPES_NAMES);
}
}
68 changes: 68 additions & 0 deletions src/Om/Converters/HashModel/StandardClassConverter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

namespace Talleu\RedisOm\Om\Converters\HashModel;

use Talleu\RedisOm\Om\Converters\AbstractStandardClassConverter;

final class StandardClassConverter extends AbstractStandardClassConverter
{
public function convert($data, ?array $hashData = [], ?string $parentProperty = null, ?string $parentPropertyType = null): array
{
foreach ($data as $key => $value) {

$valueType = is_object($value) ? get_class($value) : gettype($value);
$converter = ConverterFactory::getConverter($valueType, $value);

if (!$converter) {
continue;
}

if ($converter instanceof HashObjectConverter || $converter instanceof ArrayConverter || $converter instanceof StandardClassConverter) {
$propertyKey = $parentProperty ? sprintf("%s.%s", $parentProperty, $key) : $key;
$hashData[sprintf("%s.%s.#type", $parentProperty, $key)] = $valueType;
$hashData = $converter->convert(data: $value, hashData: $hashData, parentProperty: $propertyKey, parentPropertyType: $valueType);
continue;
}

$convertedValue = $converter->convert($value);

if ($parentProperty) {
$hashData[sprintf("%s.%s", $parentProperty, $key)] = $convertedValue;

if ('string' !== $valueType) {
$hashData[sprintf("%s.%s.#type", $parentProperty, $key)] = $valueType;
}

continue;
}

$hashData[$key] = $convertedValue;
}

return $hashData;
}

public function revert($data, string $type): \stdClass
{
$revertedStdClass = new \stdClass();
foreach ($data as $key => $value) {

if (is_array($value) && array_key_exists('#type', $value)) {
$valueType = $value['#type'];
unset($value['#type']);
} else {
$valueType = gettype($value);
}
$reverter = ConverterFactory::getReverter($valueType, $value);
if (!$reverter) {
continue;
}

$revertedStdClass->{$key} = $reverter->revert($value, $valueType);
}

return $revertedStdClass;
}
}
Loading