Skip to content

Commit

Permalink
feat(cache): adds caching capabilities for results
Browse files Browse the repository at this point in the history
  • Loading branch information
diego-ninja committed Dec 4, 2024
1 parent ed4441d commit ecba2e5
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 0 deletions.
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,51 @@ $result->confidence(); // ?float: confidence level (if available)
$result->categories(); // ?array: detected categories (if available)
```

## Response Caching

External service responses are automatically cached to improve performance and reduce API calls. By default, all external services (PurgoMalum, Azure AI, Perspective AI, and Tisane AI) will cache their responses for 1 hour.

The local censor service is not cached as it's already performant enough.

### Configuring Cache

You can configure the cache TTL and cache store in your `.env` file:

```env
CENSOR_CACHE_ENABLED=true # Enable caching (default: true)
CENSOR_CACHE_TTL=3600 # Cache duration in seconds (default: 1 hour)
CENSOR_CACHE_STORE=redis # Cache store (default: file)
```

Or in your `config/censor.php`:

```php
'cache' => [
'enabled' => env('CENSOR_CACHE_ENABLED', true),
'store' => env('CENSOR_CACHE_STORE', 'file'),
'ttl' => env('CENSOR_CACHE_TTL', 60),
],
```

The caching system uses Laravel's cache system, so it will respect your cache driver configuration (`config/cache.php`). You can use any cache driver supported by Laravel (Redis, Memcached, file, etc.).

### Cache Keys

Cache keys are generated using the following format:
```
censor:{ServiceName}:{md5(text)}
```

For example:
```
censor:PurgoMalum:a1b2c3d4e5f6g7h8i9j0
```

This ensures unique caching for:
- Different services checking the same text
- Same service checking different texts
- Different environments using the same cache store

## Custom Dictionaries

You can add your own dictionaries or modify existing ones:
Expand Down
12 changes: 12 additions & 0 deletions config/censor.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,16 @@
'censor' => [],
],

/*
|--------------------------------------------------------------------------
| Cache configuration
|--------------------------------------------------------------------------
| Define the configuration for the cache
|
*/
'cache' => [
'enabled' => env('CENSOR_CACHE_ENABLED', true),
'store' => env('CENSOR_CACHE_STORE', 'file'),
'ttl' => env('CENSOR_CACHE_TTL', 60),
],
];
34 changes: 34 additions & 0 deletions src/Decorators/CachedProfanityChecker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Ninja\Censor\Decorators;

use Illuminate\Support\Facades\Cache;
use Ninja\Censor\Contracts\ProfanityChecker;
use Ninja\Censor\Contracts\Result;

final readonly class CachedProfanityChecker implements ProfanityChecker
{
public function __construct(
private ProfanityChecker $checker,
private int $ttl = 3600 // 1 hour by default
) {}

public function check(string $text): Result
{
$cacheKey = sprintf(
'censor:%s:%s',
class_basename($this->checker),
md5($text)
);

/** @var string $store */
$store = config('censor.cache.store', 'default');

/** @var Result $result */
$result = Cache::store($store)->remember($cacheKey, $this->ttl, function () use ($text): Result {
return $this->checker->check($text);
});

return $result;
}
}
10 changes: 10 additions & 0 deletions src/Factories/ProfanityCheckerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Ninja\Censor\Checkers\PurgoMalum;
use Ninja\Censor\Checkers\TisaneAI;
use Ninja\Censor\Contracts\ProfanityChecker;
use Ninja\Censor\Decorators\CachedProfanityChecker;
use Ninja\Censor\Enums\Service;

final readonly class ProfanityCheckerFactory
Expand All @@ -26,6 +27,15 @@ public static function create(Service $service, array $config = []): ProfanityCh
Service::Azure => AzureAI::class,
};

if (config('censor.cache.enabled', false) === true) {
$ttl = config('censor.cache.ttl', 3600);
if (is_int($ttl) === false) {
$ttl = 3600;
}

return new CachedProfanityChecker(new $class(...$config), $ttl);
}

return new $class(...$config);
}
}
1 change: 1 addition & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ protected function defineEnvironment($app): void
$app['config']->set('censor.languages', ['en']);
$app['config']->set('censor.dictionary_path', __DIR__.'/../resources/dict');
$app['config']->set('censor.default_service', 'local');
$app['config']->set('censor.cache.enabled', false);
$app['config']->set('censor.replacements', [
'a' => '(a|a\.|a\-|4|@|Á|á|À|Â|à|Â|â|Ä|ä|Ã|ã|Å|å|α|Δ|Λ|λ)',
'b' => '(b|b\.|b\-|8|\|3|ß|Β|β)',
Expand Down
19 changes: 19 additions & 0 deletions tests/Unit/Factories/ProfanityCheckerFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,22 @@
[Service::Perspective, PerspectiveAI::class],
[Service::Tisane, TisaneAI::class],
]);

test('factory creates cached decorator when cache is enabled', function (Service $service, string $expectedClass) {
config(['censor.cache.enabled' => true]);
$config = match ($service) {
Service::Azure => ['endpoint' => 'test', 'key' => 'test', 'version' => '2024-09-01'],
Service::Perspective, Service::Tisane => ['key' => 'test'],
default => []
};

$checker = ProfanityCheckerFactory::create($service, $config, true);
expect($checker)->toBeInstanceOf(\Ninja\Censor\Decorators\CachedProfanityChecker::class);

})->with([
[Service::Local, Censor::class],
[Service::PurgoMalum, PurgoMalum::class],
[Service::Azure, AzureAI::class],
[Service::Perspective, PerspectiveAI::class],
[Service::Tisane, TisaneAI::class],
]);

0 comments on commit ecba2e5

Please sign in to comment.