# Welcome

Welcome to the documentation for [midnite81/guardian](https://github.com/midnite81/guardian).

## What is Guardian?

[**Guardian**](https://github.com/midnite81/guardian) is a package designed to wrap HTTP requests where rate-limiting or controlled error handling is crucial. It allows you to make requests while respecting rate limits and manage potential errors efficiently.


# Installation

{% hint style="info" %}
This PHP package is for PHP 8.2 and greater
{% endhint %}

## Requirements

* PHP 8.2 or higher
* Composer

## Basic Installation

You can install Guardian via Composer by running the following command in your project directory:

```bash
composer require midnite81/guardian
```

## Framework Integration

While Guardian is framework-agnostic, it comes with built-in support for Laravel.

### Laravel Integration

For Laravel projects, Guardian provides a Service Provider and a Facade, which are automatically registered in most cases.

#### Automatic Registration (Laravel 5.5+)

If you're using Laravel 5.5 or higher with package auto-discovery enabled, you don't need to manually register the service provider or facade.

#### Manual Registration

If you've disabled auto-discovery or are using an older version of Laravel, you'll need to manually register the service provider and facade.

**For Laravel 11+:**

Add the following to your `bootstrap/app.php`:

```php
->withProviders([
    \Midnite81\Guardian\Providers\GuardianServiceProvider::class,
])
```

**For Laravel 10 and below:**

Add the following to your `config/app.php`:

```php
'providers' => [
    // Other service providers...
    \Midnite81\Guardian\Providers\GuardianServiceProvider::class,
],

'aliases' => [
    // Other facades...
    'Guardian' => \Midnite81\Guardian\Facades\Guardian::class,
],
```


# Getting a Guardian instance

[**Guardian**](https://github.com/midnite81/guardian) instances can be obtained in several ways, with the recommended method being via Guardian factories.

## Methods to Get a Guardian Instance

1. [Factory Instances](#factory-instances)
2. [Direct Class Instantiation](#direct-class-instantiation)
3. [Dependency Injection (Laravel)](#dependency-injection-laravel)
4. [Facade (Laravel)](#facade-laravel)

## Required and Optional Arguments

The Guardian class requires the following arguments:

| Argument       | Type                                         | Description                                                 |
| -------------- | -------------------------------------------- | ----------------------------------------------------------- |
| `$identifier`  | `string`                                     | Used to register rate limits and error-handling             |
| `$cache`       | `CacheInterface`                             | Cache driver to store rate-limiting and error-handling data |
| `$rules`       | `RateLimitingRulesetInterface\|array\|null`  | Ruleset or array of `RateLimitRule` rules (optional)        |
| `$errorRules`  | `ErrorHandlingRulesetInterface\|array\|null` | Ruleset or array of `ErrorHandlingRule` rules (optional)    |
| `$cachePrefix` | `string`                                     | Prefix for cache keys (default: 'guardian')                 |

> **Note:** In Laravel's `make` method, `$cache` is not required as it defaults to Laravel's cache.

## Factory Instances

### Non-Laravel Projects

```php
use Midnite81\Guardian\Factories\GuardianFactory;
use Midnite81\Guardian\Store\FileStore;

$guardian = GuardianFactory::create(
    'weather-conditions',
    new FileStore('/path/to/cache'),
    [RateLimitRule::allow(100)->perMinute()],
    [ErrorHandlingRule::allowFailures(5)->perMinute()]
);
```

### Laravel Projects

```php
use Midnite81\Guardian\Factories\LaravelGuardianFactory;

// Using Laravel's built-in cache
$guardian = LaravelGuardianFactory::make(
    'weather-conditions',
    [RateLimitRule::allow(100)->perMinute()],
    [ErrorHandlingRule::allowFailures(5)->perMinute()]
);

// Using a custom cache
$guardian = LaravelGuardianFactory::create(
    'weather-conditions',
    new FileStore('/path/to/cache'),
    [RateLimitRule::allow(100)->perMinute()],
    [ErrorHandlingRule::allowFailures(5)->perMinute()]
);
```

## Direct Class Instantiation

```php
use Midnite81\Guardian\Guardian;
use Midnite81\Guardian\Store\FileStore;

$guardian = new Guardian(
    'spotify-playlist',
    new FileStore('/path/to/cache'),
    [RateLimitRule::allow(100)->perMinute()],
    [ErrorHandlingRule::allowFailures(5)->perMinute()]
);
```

## Dependency Injection (Laravel)

```php
use Midnite81\Guardian\Guardian;

class MyController
{
    public function __construct(protected Guardian $guardian)
    {
        $this->guardian->setIdentifier('spotify-playlist')
            ->setCache($customCache) // Optional: defaults to Laravel's cache
            ->addRules([RateLimitRule::allow(100)->perMinute()])
            ->addErrorRules([ErrorHandlingRule::allowFailures(5)->perMinute()]);
    }
}
```

## Facade (Laravel)

```php
use Midnite81\Guardian\Facades\Guardian;

// Using Laravel's built-in cache
$guardian = Guardian::make(
    'weather-conditions', 
    [RateLimitRule::allow(100)->perMinute()]
);

// Using a custom cache
$guardian = Guardian::create(
    'weather-conditions',
    new FileStore('/path/to/cache'),
    [RateLimitRule::allow(100)->perMinute()],
    [ErrorHandlingRule::allowFailures(5)->perMinute()]
);
```


# Choosing an identifier

## Overview

In the Guardian rate limiting system, the identifier is a crucial component that determines how rate limits are applied and tracked. This guide will help you understand the importance of identifiers, how to choose them effectively, and how they are processed within the system.

## Importance of Identifiers

The identifier serves as a unique key for storing and retrieving rate limit data. Each distinct identifier will have its own set of cache entries, allowing for granular control over rate limiting for different resources, users, or actions.

## Relationship with Cache Stores

It's essential to understand that **cache stores are made against the identifier**. This means:

1. Each unique identifier will have its own separate cache entries.
2. Rate limits are tracked and enforced independently for each identifier.
3. Clearing or resetting rate limits for one identifier won't affect others.

## Choosing an Identifier

When selecting an identifier, consider the following:

1. **Uniqueness**: Choose identifiers that uniquely represent the entity or action you're rate limiting.
2. **Granularity**: More specific identifiers allow for more fine-grained control.
3. **Consistency**: Use consistent identifiers for the same entities or actions across your application.

Examples of good identifiers:

* User IDs: `user_123`
* IP addresses: `ip_192.168.1.1`
* API endpoints: `api_get_users`
* Combinations: `user_123_api_get_users`

## Identifier Processing

Guardian processes the identifier to ensure it's safe for use as a cache key. Here's how the identifier is parsed:

1. **Character Sanitization**: Only alphanumeric characters, underscores, and hyphens are allowed. All other characters are replaced with underscores.

   ```php
   $safe = preg_replace('/[^a-zA-Z0-9_-]/', '_', $identifier);
   ```
2. **Prefix Handling**:
   * If a prefix is provided (default is 'guardian'), it's sanitized and added to the beginning of the identifier.
   * If no prefix is provided and the sanitized identifier doesn't start with a letter, 'id\_' is prepended.
3. **Duplicate Character Removal**: Consecutive underscores are reduced to a single underscore.
4. **Trailing Underscore Removal**: Any trailing underscore is removed from the identifier.
5. **Case Conversion**: The entire identifier is converted to lowercase.
6. **Length Limitation**: The final identifier is truncated to a maximum of 100 characters (including the prefix).
7. **Empty Check**: If the resulting identifier is empty or just 'id\_', an exception is thrown.

## Example

```php
$originalIdentifier = "user@123_action/get";
$guardian = new Guardian($originalIdentifier, $cache);

// Internally, this becomes: "guardian_user_123_action_get"
```

## Best Practices

1. **Use Meaningful Identifiers**: Choose identifiers that clearly represent what's being rate limited.
2. **Be Consistent**: Use the same identifier format for similar entities or actions.
3. **Consider Combining Factors**: For more specific rate limiting, combine multiple factors in your identifier (e.g., `"{$userId}_{$action}_{$resourceId}"`).
4. **Be Aware of Truncation**: Remember that very long identifiers will be truncated to 100 characters.
5. **Avoid Relying on Case**: Since identifiers are converted to lowercase, don't rely on case for uniqueness.
6. **Consider Special Character Replacement**: Be aware that special characters will be replaced with underscores.

## Security Considerations

* Don't include sensitive information in identifiers, as they may be logged or visible in cache systems.
* Be cautious about using easily guessable identifiers that could allow bad actors to manipulate rate limits.

## Troubleshooting

* If rate limits aren't behaving as expected, double-check that you're using consistent identifiers.
* Remember that changing an identifier (even slightly) will result in a new set of cache entries and rate limit tracking.

By carefully choosing and consistently using identifiers, you can create a robust and granular rate limiting system with Guardian. The identifier is key to how Guardian tracks and enforces rate limits, so give careful consideration to your identifier strategy when implementing Guardian in your application.


# Firing off an HTTP request

Now that you have your Guardian instance, we can wrap our HTTP request to let Guardian handle any rate-limiting or error-handling rules we may have. We'll look at Cache Drivers and Rules in more detail in later sections.

This package doesn't limit how you fire your HTTP requests. You can write any code you'd like to run to get your data in a closure in the `send` method. Let's assume for now the request is always successful.

```php
use Midnite81\Guardian\Factories\GuardianFactory;
use Midnite81\Guardian\Store\RedisStore;
use Midnite81\Guardian\Rules\RateLimitRule;
use GuzzleHttp\Client;

$guardian = GuardianFactory::create(
    'blog-post-1',
    new RedisStore($options),
    [RateLimitRule::allow(6)->perMinute()],
);

$result = $guardian->send(function () {
    $client = new Client();
    $response = $client->get('https://jsonplaceholder.typicode.com/posts/1');
    return json_decode($response->getBody()->getContents(), true);
});

// $result now contains the decoded JSON response
```

## Exception Handling

Guardian throws various exceptions to help you manage rate limiting and error scenarios. Here's how to handle them:

### RulePreventsExecutionException

In our example above, we're hitting an API endpoint and getting some JSON back. However, our rules are rate-limiting our ability to get the data to only 6 requests per minute.

If we exceed this limit, Guardian will throw a `RulePreventsExecutionException`, unless you specify `false` as a second argument in the `send` method, in which case it'll return `null`.

Here's how to catch this exception:

```php
use Midnite81\Guardian\Exceptions\RulePreventsExecutionException;

try {
    $result = $guardian->send(function () {
        $client = new Client();
        $response = $client->get('https://jsonplaceholder.typicode.com/posts/1');
        return json_decode($response->getBody()->getContents(), true);
    });
} catch (RulePreventsExecutionException $e) { 
    return "Rate limit exceeded: " . $e->getMessage();
}
```

### RateLimitExceededException

There may be times when you set rules to prevent going over your rate limiting quota, but the server says you've already reached a limit. To prevent continued spamming of an API, we can throw a `RateLimitExceededException` in the callback. Guardian will then prevent any further API calls until the expiry time has passed.

```php
use Midnite81\Guardian\Exceptions\RateLimitExceededException;
use GuzzleHttp\Exception\ClientException;

$guardian->send(function () use ($httpClient) {
    try {
        $response = $httpClient->get('https://api.example.com/endpoint');
        return $response->getBody()->getContents();
    } catch (ClientException $e) {
        if ($e->getResponse()->getStatusCode() === 429) {
            $retryAfter = $e->getResponse()->getHeaderLine('Retry-After');
            throw new RateLimitExceededException($retryAfter, 'Rate limit exceeded by external API');
        }
        // Handle other client exceptions...
        throw $e;
    }
});
```

### Store Exceptions

Guardian uses various storage mechanisms to keep track of rate limits and other data. Each storage mechanism can throw its own type of exception, all of which extend the base `StoreException`. Here are the specific store exceptions:

* `DatabaseStoreException`: Thrown when there's an issue with database operations.
* `FileStoreException`: Thrown when there's a problem with file-based storage operations.
* `RedisStoreException`: Thrown when there's an issue with Redis operations.

You can catch these exceptions individually or catch the base `StoreException` to handle all storage-related issues:

```php
use Midnite81\Guardian\Exceptions\Store\StoreException;
use Midnite81\Guardian\Exceptions\Store\DatabaseStoreException;
use Midnite81\Guardian\Exceptions\Store\FileStoreException;
use Midnite81\Guardian\Exceptions\Store\RedisStoreException;

try {
    $result = $guardian->send(function () {
        // Your API call here
    });
} catch (DatabaseStoreException $e) {
    // Handle database-specific storage issues
} catch (FileStoreException $e) {
    // Handle file-specific storage issues
} catch (RedisStoreException $e) {
    // Handle Redis-specific storage issues
} catch (StoreException $e) {
    // Handle any other storage-related issues
}
```

### Other Exceptions

You should be prepared to catch any other exceptions that the callback may throw. A general `catch (Exception $e)` is a good approach to prevent your application from potentially displaying an internal server error to users.

```php
use Exception;

try {
    $result = $guardian->send(function () {
        // Your API call here
    });
} catch (RulePreventsExecutionException $e) {
    // Handle rate limiting
} catch (RateLimitExceededException $e) {
    // Handle external API rate limiting
} catch (StoreException $e) {
    // Handle storage issues
} catch (Exception $e) {
    // Handle any other unexpected exceptions
    return "An unexpected error occurred: " . $e->getMessage();
}
```

By handling these exceptions, you can gracefully manage rate limiting, storage issues, and other potential errors in your application.


# Cache Stores

Cache stores are a fundamental component of Guardian, playing a crucial role in its rate limiting and error handling functionality. These stores provide the underlying mechanism for persisting and retrieving data related to request limits, error counts, and other essential metrics. By offering various cache store implementations, Guardian ensures flexibility and adaptability across different environments and requirements, allowing for efficient management of rate limiting rules and error handling policies.

## Available Cache Stores

Guardian provides multiple cache store implementations to suit different environments and requirements. This document outlines the available cache stores and how to construct them.

1. [LaravelStore](#laravelstore)
2. [FileStore](#filestore)
3. [DatabaseStore](#databasestore)
4. [RedisStore](#redisstore)

## LaravelStore

The `LaravelStore` is designed for use within Laravel applications. It utilizes Laravel's built-in caching system.

### Construction

```php
use Illuminate\Cache\Repository;
use Midnite81\Guardian\Store\LaravelStore;

$laravelCache = app(Repository::class);
$store = new LaravelStore($laravelCache);
```

## FileStore

The `FileStore` uses the local filesystem for caching. It's suitable for applications without a dedicated caching system.

### Construction

```php
use Midnite81\Guardian\Store\FileStore;

$basePath = '/path/to/cache/directory';
$store = new FileStore($basePath);
```

### Important Notes

1. **Permissions**: Ensure that your application has the necessary read and write permissions for the specified cache directory. The FileStore needs to be able to create, read, update, and delete files in this directory.
2. **Directory Creation**: The FileStore will attempt to create the cache directory if it doesn't exist. However, it needs the appropriate permissions to do so.
3. **Security**: Choose a directory that is not publicly accessible to prevent unauthorized access to cached data.
4. **Disk Space**: Monitor the disk space usage of your cache directory, especially for long-running applications or those with high cache volume.

Example of setting proper permissions (Unix-based systems):

```bash
sudo mkdir -p /path/to/cache/directory
sudo chown -R www-data:www-data /path/to/cache/directory
sudo chmod -R 755 /path/to/cache/directory
```

Replace `www-data` with the user under which your web server runs.

## DatabaseStore

The `DatabaseStore` uses a database table for caching. It's useful when you want to persist cache data in a database.

**Important**: The DatabaseStore will automatically create the necessary caching table if it doesn't exist.

### Construction

```php
use PDO;
use Midnite81\Guardian\Store\DatabaseStore;

$pdo = new PDO('mysql:host=localhost;dbname=your_database', 'username', 'password');
$tableName = 'guardian_cache'; // optional, defaults to 'guardian_cache'
$store = new DatabaseStore($pdo, $tableName);
```

When you create a new instance of DatabaseStore, it will:

1. Check if the specified table (default: 'guardian\_cache') exists.
2. If the table doesn't exist, it will automatically create it with the following structure:
   * `key` (VARCHAR(255), PRIMARY KEY)
   * `value` (TEXT)
   * `expiration` (INT)

This means you don't need to manually create the table or run any migrations. The DatabaseStore handles the table creation for you.

## RedisStore

The `RedisStore` uses Redis for caching, providing fast in-memory caching with persistence.

### Construction

```php
use Redis;
use Midnite81\Guardian\Store\RedisStore;

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$prefix = 'guardian:'; // optional, defaults to 'guardian:'
$store = new RedisStore($redis, $prefix);
```

## Using Cache Stores with Guardian

Once you've constructed a cache store, you can use it when creating a Guardian instance:

```php
use Midnite81\Guardian\Guardian;

$guardian = new Guardian('your-identifier', $store);
```

Replace `$store` with your chosen cache store instance.

## Choosing a Cache Store

* **LaravelStore**: Best for Laravel applications, as it integrates seamlessly with Laravel's caching system.
* **FileStore**: Good for simple applications or development environments where you don't want to set up a separate caching system. Ensure proper file permissions are set.
* **DatabaseStore**: Useful when you want to persist cache data in a database, which can be beneficial for debugging or data analysis. It automatically creates the required table.
* **RedisStore**: Excellent for high-performance requirements, as Redis provides fast in-memory caching with optional persistence.

Choose the cache store that best fits your application's needs and infrastructure.


# Creating your own cache driver

Guardian allows you to create and use custom cache drivers to suit your specific needs. This guide will walk you through the process of creating your own cache driver.

## Step 1: Implement the CacheInterface

Your custom cache driver must implement the `Midnite81\Guardian\Contracts\Store\CacheInterface`. This interface defines the methods that your cache driver needs to implement:

```php
namespace Midnite81\Guardian\Contracts\Store;

use DateInterval;
use DateTimeInterface;

interface CacheInterface
{
    public function get(string $key, mixed $default = null): mixed;
    public function has(string $key): bool;
    public function put(string $key, mixed $value, DateInterval|DateTimeInterface|int|null $ttl = null): bool;
    public function forget(string $key): bool;
}
```

## Step 2: Create Your Custom Cache Driver

Create a new class that implements the `CacheInterface`. Here's an example skeleton:

```php
namespace YourNamespace;

use DateInterval;
use DateTimeInterface;
use Midnite81\Guardian\Contracts\Store\CacheInterface;

class YourCustomCacheDriver implements CacheInterface
{
    public function get(string $key, mixed $default = null): mixed
    {
        // Implement get logic
    }

    public function has(string $key): bool
    {
        // Implement has logic
    }

    public function put(string $key, mixed $value, DateInterval|DateTimeInterface|int|null $ttl = null): bool
    {
        // Implement put logic
    }

    public function forget(string $key): bool
    {
        // Implement forget logic
    }
}
```

## Step 3: Implement the Required Methods

Fill in the logic for each method based on your caching mechanism. Here's a brief description of what each method should do:

* `get`: Retrieve a value from the cache by its key. Return the default value if the key doesn't exist.
* `has`: Check if a key exists in the cache.
* `put`: Store a value in the cache with an optional TTL (Time To Live).
* `forget`: Remove a value from the cache by its key.

## Step 4: Use Your Custom Cache Driver

To use your custom cache driver with Guardian, pass an instance of your driver when creating a Guardian instance:

```php
use Midnite81\Guardian\Guardian;
use YourNamespace\YourCustomCacheDriver;

$customCache = new YourCustomCacheDriver();
$guardian = new Guardian('your-identifier', $customCache);
```

## Best Practices

1. **Error Handling**: Implement proper error handling in your cache driver. You may want to create a custom exception class for cache-related errors.
2. **Serialization**: Consider serializing and deserializing complex data types when storing and retrieving from the cache.
3. **TTL Handling**: Implement proper handling of the TTL parameter in the `put` method. This may involve converting `DateInterval` and `DateTimeInterface` to seconds.
4. **Performance**: Ensure your cache driver is optimized for performance, especially if you're dealing with high-traffic applications.
5. **Testing**: Write unit tests for your custom cache driver to ensure it behaves correctly and integrates well with Guardian.

By following these steps and best practices, you can create a custom cache driver that integrates seamlessly with the Guardian package and meets your specific caching requirements.


# Clearing the Cache

The Guardian library provides a method to clear all cache entries associated with a specific Guardian instance. This can be useful when you want to reset all rate limiting and error handling counters for a particular identifier.

### Using the `clearCache()` method

To clear the cache, you need an instantiated Guardian instance. Here's how you can use the `clearCache()` method:

```php
// Assuming you have an instantiated Guardian instance
$guardian = new Guardian('your-identifier', $cacheImplementation);

// Clear the cache for this Guardian instance
$result = $guardian->clearCache();

if ($result) {
    echo "Cache cleared successfully!";
} else {
    echo "Some or all cache entries could not be cleared.";
}
```

#### Important Notes:

1. **Instantiated Guardian Required**: You must have an instantiated Guardian object to call the `clearCache()` method. This method is not static and operates on the specific instance's cache entries.
2. **Identifier-Specific**: The `clearCache()` method only clears cache entries related to the Guardian instance's identifier. It does not affect cache entries for other Guardian instances or other parts of your application.
3. **Return Value**: The method returns a boolean value:
   * `true` if all cache entries were successfully cleared
   * `false` if some or all cache entries could not be cleared
4. **Cache Implementation**: The effectiveness of this method depends on the underlying cache implementation you're using (e.g., Redis, file system, database). Ensure your cache system supports deletion operations.
5. **Use with Caution**: Clearing the cache resets all rate limiting and error handling counters. This could potentially allow previously rate-limited operations to proceed. Use this method judiciously, typically for maintenance or reset purposes.

### When to Use `clearCache()`

Consider using `clearCache()` in scenarios such as:

* Resetting rate limits after a maintenance period
* Clearing error counters after resolving a system-wide issue
* Implementing an admin function to reset limits for a specific user or service

Remember, you need access to the specific Guardian instance to clear its cache. In a typical application setup, you might need to retrieve or recreate the Guardian instance with the correct identifier before calling `clearCache()`.


# What are rulesets

Rulesets in Guardian allow you to define a series of rules for Rate-Limiting and Error-Handling. Instead of defining rules each time you construct a Guardian instance, you can create pre-determined sets of rules to pass to Guardian. This approach promotes code reusability and cleaner organization of your rate limiting and error handling logic.

## Types of Rulesets

Guardian supports two types of rulesets:

1. Rate Limiting Rulesets
2. Error Handling Rulesets

Both types of rulesets require you to implement a `rules()` method, but they differ in the type of rules they return:

* Rate Limiting Rulesets return an array of `RateLimitRule` objects
* Error Handling Rulesets return an array of `ErrorHandlingRule` objects

## Implementing Rate Limiting Rulesets

To create a Rate Limiting Ruleset, your class must implement the `Midnite81\Guardian\Contracts\Rulesets\RateLimitingRulesetInterface`. However, for convenience, you can extend the `Midnite81\Guardian\Rulesets\AbstractRateLimitingRuleset` class, which implements the interface and provides core logic.

Here's an example of a custom Rate Limiting Ruleset:

```php
<?php

declare(strict_types=1);

namespace App\Rulesets;

use Midnite81\Guardian\Rulesets\AbstractRateLimitingRuleset;
use Midnite81\Guardian\Rules\RateLimitRule;

class MyCustomRateLimitingRuleset extends AbstractRateLimitingRuleset
{
    /**
     * {@inheritDoc}
     */
    public function rules(): array
    {
        return [
            RateLimitRule::allow(20)->perHour(),
            RateLimitRule::allow(1000)->perDay(),
        ];
    }
}
```

## Implementing Error Handling Rulesets

For Error Handling Rulesets, your class must implement the `Midnite81\Guardian\Contracts\Rulesets\ErrorHandlingRulesetInterface`. Similar to Rate Limiting Rulesets, you can extend the `Midnite81\Guardian\Rulesets\AbstractErrorHandlingRuleset` for convenience.

Here's an example of a custom Error Handling Ruleset:

```php
<?php

declare(strict_types=1);

namespace App\Rulesets;

use Midnite81\Guardian\Rulesets\AbstractErrorHandlingRuleset;
use Midnite81\Guardian\Rules\ErrorHandlingRule;

class MyCustomErrorHandlingRuleset extends AbstractErrorHandlingRuleset
{
    /**
     * {@inheritDoc}
     */
    public function rules(): array
    {
        return [
            ErrorHandlingRule::allowFailures(5)->perHour(),
        ];
    }
}
```

## Using Custom Rulesets with Guardian

You can use your custom rulesets with Guardian in two ways:

1. Pass the ruleset when creating the Guardian instance:

```php
use App\Rulesets\MyCustomRateLimitingRuleset;
use App\Rulesets\MyCustomErrorHandlingRuleset;
use Midnite81\Guardian\Factories\GuardianFactory;
use Midnite81\Guardian\Store\RedisStore;

$guardian = GuardianFactory::create(
    'my-api',
    new RedisStore($options),
    new MyCustomRateLimitingRuleset(),
    new MyCustomErrorHandlingRuleset()
);
```

2. Set the rulesets after creating the Guardian instance:

```php
$guardian->setRules(new MyCustomRateLimitingRuleset());
$guardian->setErrorRules(new MyCustomErrorHandlingRuleset());
```

## Adding Additional Rules to a Guardian Instance

In some cases, you might want to add additional rules to a specific Guardian instance without modifying the original ruleset. Guardian provides methods to add rules dynamically:

### Adding Rate Limiting Rules

You can use the `addRules()` method to add one or more rate limiting rules to an existing Guardian instance:

```php
use Midnite81\Guardian\Rules\RateLimitRule;

$guardian->addRules([
    RateLimitRule::allow(5)->perMinute(),
    RateLimitRule::allow(100)->perHour(),
]);
```

### Adding Error Handling Rules

Similarly, you can use the `addErrorRules()` method to add one or more error handling rules:

```php
use Midnite81\Guardian\Rules\ErrorHandlingRule;

$guardian->addErrorRules([
    ErrorHandlingRule::allowFailures(3)->perMinute(),
]);
```

These methods are useful when you need to:

1. Add context-specific rules for a particular Guardian instance.
2. Dynamically adjust rules based on runtime conditions.
3. Temporarily modify the ruleset for specific operations without affecting the original ruleset.

Remember that rules added this way only apply to the specific Guardian instance and do not modify the original ruleset.

## Benefits of Using Rulesets

1. **Reusability**: Define rules once and reuse them across multiple Guardian instances.
2. **Organization**: Keep your rate limiting and error handling logic separate from your main application code.
3. **Flexibility**: Easily switch between different sets of rules for different scenarios or environments.
4. **Maintainability**: Update rules in one place, affecting all Guardian instances using that ruleset.
5. **Customization**: Ability to add instance-specific rules when needed, without modifying the base ruleset.

By leveraging rulesets and the ability to add rules dynamically, you can create more modular, maintainable, and flexible rate limiting and error handling strategies in your application.


# Rate Limiting Rules

Rate limiting rules define how many requests are allowed within a given time frame. These rules are crucial for controlling access to your resources and preventing abuse or overuse of your services.

## Creating Rate Limit Rules

You can create rate limit rules using the `RateLimitRule` class. This class provides a fluent interface for defining rules, making them easy to read and understand.

```php
use Midnite81\Guardian\Rules\RateLimitRule;
use Midnite81\Guardian\Enums\Interval;

// Allow 100 requests per minute
$rule1 = RateLimitRule::allow(100)->perMinute();

// Allow 1000 requests per hour
$rule2 = RateLimitRule::allow(1000)->perHour();

// Allow 10000 requests per day
$rule3 = RateLimitRule::allow(10000)->perDay();

// Allow 25 requests every 3 hours
$rule4 = RateLimitRule::allow(25)->every(3, Interval::HOUR);
```

## Available Methods

The `RateLimitRule` class provides several methods to define your rate limiting rules:

### Static Factory Method

* `allow(int $limit)`: Starts the rule definition with the number of allowed requests.

### Time Interval Methods

* `every(int $amount, Interval $unit)`: Defines a custom time interval.
* `perSecond()`: Sets the interval to one second.
* `perSeconds(int $seconds)`: Sets the interval to a specified number of seconds.
* `perMinute()`: Sets the interval to one minute.
* `perMinutes(int $minutes)`: Sets the interval to a specified number of minutes.
* `perHour()`: Sets the interval to one hour.
* `perHours(int $hours)`: Sets the interval to a specified number of hours.
* `perDay()`: Sets the interval to one day.
* `perDays(int $days)`: Sets the interval to a specified number of days.
* `perWeek()`: Sets the interval to one week.
* `perWeeks(int $weeks)`: Sets the interval to a specified number of weeks.
* `perMonth()`: Sets the interval to one month.
* `perMonths(int $months)`: Sets the interval to a specified number of months.

### Expiration Methods

* `dailyUntil(string $time)`: Sets the rule to expire daily at a specific time (format: 'H:i').
* `untilMidnightTonight()`: Sets the rule to expire at midnight.
* `untilEndOfMonth()`: Sets the rule to expire at the end of the current month.

### Getter Methods

* `getLimit()`: Returns the number of allowed requests.
* `getInterval()`: Returns the interval enum value.
* `getDuration()`: Returns the duration of the interval.
* `getUntil()`: Returns the expiration time, if set.
* `getTotalSeconds()`: Returns the total number of seconds for the interval.
* `getKey(string $prefix = '', string $suffix = '')`: Generates a unique key for the rule.

## Examples

Here are some more complex examples of how you can use these rules:

```php
// Allow 500 requests per hour, resetting at midnight
$rule = RateLimitRule::allow(500)->perHour()->untilMidnightTonight();

// Allow 1000 requests per day, expiring at 23:59
$rule = RateLimitRule::allow(1000)->perDay()->dailyUntil('23:59');

// Allow 5000 requests per month, resetting at the end of each month
$rule = RateLimitRule::allow(5000)->perMonth()->untilEndOfMonth();
```

## Using Rate Limit Rules

Once you've defined your rules, you can add them to your Guardian instance:

```php
use Midnite81\Guardian\Factories\GuardianFactory;
use Midnite81\Guardian\Store\RedisStore;

$guardian = GuardianFactory::create(
    'my-api',
    new RedisStore($redisOptions),
    [
        RateLimitRule::allow(100)->perMinute(),
        RateLimitRule::allow(1000)->perHour(),
    ]
);
```

These rules will now be applied to all requests handled by this Guardian instance.

## Best Practices

1. **Combine rules**: Use multiple rules to create more complex rate limiting strategies. For example, you might allow a high number of requests per day, but still limit the per-minute rate to prevent sudden spikes.
2. **Use appropriate intervals**: Choose intervals that make sense for your application. For an API, per-second or per-minute limits might be appropriate, while for a bulk operation, per-hour or per-day limits might make more sense.
3. **Consider user tiers**: If your application has different user tiers, you might create different sets of rules for each tier.
4. **Monitor and adjust**: Regularly review your rate limits and adjust them based on your application's performance and user behavior.

By effectively using rate limiting rules, you can protect your resources, ensure fair usage, and maintain the performance and reliability of your application.


# Error Handling Rules

Error handling rules define how many errors are allowed before Guardian takes action. These rules help you manage and respond to failures in your application, allowing you to set thresholds for acceptable error rates.

## Creating Error Handling Rules

You can create error handling rules using the `ErrorHandlingRule` class. This class provides a fluent interface for defining rules, making them easy to read and understand.

```php
use Midnite81\Guardian\Rules\ErrorHandlingRule;
use Midnite81\Guardian\Enums\Interval;

// Allow 5 failures per minute, before throwing an error
$rule1 = ErrorHandlingRule::allowFailures(5)->perMinute();

// Equivalent to $rule1, but with explicit thenThrow()
$rule2 = ErrorHandlingRule::allowFailures(5)->perMinute()->thenThrow();

// Allow 50 failures per hour, without throwing an exception (for monitoring purposes)
$rule3 = ErrorHandlingRule::allowFailures(50)->perHour()->thenThrow(false);

// Allow 100 failures per day, before throwing an error
$rule4 = ErrorHandlingRule::allowFailures(100)->perDay();

// Allow 20 failures every 3 hours, before throwing an error
$rule5 = ErrorHandlingRule::allowFailures(20)->perInterval(Interval::HOUR, 3);
```

**Important Note**: Simply calling `ErrorHandlingRule::allowFailures(5)` without specifying a time interval does not create a functional rule. You must always specify a time interval for the rule to take effect. For example:

```php
// This does nothing and will not be enforced
$ineffectiveRule = ErrorHandlingRule::allowFailures(5);

// This is a valid rule that will be enforced: it allows 5 failures per hour before throwing an error
$effectiveRule = ErrorHandlingRule::allowFailures(5)->perHour();
```

## Available Methods

The `ErrorHandlingRule` class provides several methods to define your error handling rules:

### Static Factory Method

* `allowFailures(int $failureThreshold)`: Starts the rule definition with the number of allowed failures. Note that this method alone is not sufficient to create a complete rule.

### Time Interval Methods

* `perInterval(Interval $interval, int $duration = 1)`: Defines a custom time interval.
* `perMinute()`: Sets the interval to one minute.
* `perMinutes(int $value)`: Sets the interval to a specified number of minutes.
* `perHour()`: Sets the interval to one hour.
* `perHours(int $value)`: Sets the interval to a specified number of hours.
* `perDay()`: Sets the interval to one day.
* `perDays(int $value)`: Sets the interval to a specified number of days.

### Expiration Method

* `untilMidnightTonight()`: Sets the rule to expire at midnight.

### Action Method

* `thenThrow(bool $shouldThrow = true)`: Specifies whether to throw an exception when the failure threshold is exceeded.

### Getter Methods

* `getFailureThreshold()`: Returns the number of allowed failures.
* `getInterval()`: Returns the interval enum value.
* `getDuration()`: Returns the duration of the interval.
* `getUntil()`: Returns the expiration time, if set.
* `shouldThrow()`: Returns whether an exception should be thrown when the threshold is exceeded.
* `getTotalSeconds()`: Returns the total number of seconds for the interval.
* `getKey(string $prefix = '', string $suffix = '')`: Generates a unique key for the rule.

## The `thenThrow()` Method

The `thenThrow()` method determines whether Guardian should throw an exception when the failure threshold is exceeded. By default, it's set to `true`.

```php
// Will throw an exception after 10 failures per hour
$rule1 = ErrorHandlingRule::allowFailures(10)->perHour();

// Equivalent to the above
$rule2 = ErrorHandlingRule::allowFailures(10)->perHour()->thenThrow();

// Will not throw an exception, even if more than 10 failures occur per hour
$rule3 = ErrorHandlingRule::allowFailures(10)->perHour()->thenThrow(false);
```

**Note**: Using `thenThrow(false)` effectively means that Guardian will track the number of failures but won't take any action when the threshold is exceeded. This can be useful for logging or monitoring purposes, but it doesn't provide any automatic error handling. Use this option with caution, as it may lead to ignoring critical errors in your application.

## Using Error Handling Rules

Once you've defined your rules, you can add them to your Guardian instance:

```php
use Midnite81\Guardian\Factories\GuardianFactory;
use Midnite81\Guardian\Store\RedisStore;

$guardian = GuardianFactory::create(
    'my-api',
    new RedisStore($redisOptions),
    null, // Rate limiting rules (null in this example)
    [
        ErrorHandlingRule::allowFailures(5)->perMinute(),
        ErrorHandlingRule::allowFailures(50)->perHour(),
    ]
);
```

These rules will now be applied to all requests handled by this Guardian instance.

## Best Practices

1. **Always specify a time interval**: Remember that an error handling rule is not complete without a time interval. Always use methods like `perMinute()`, `perHour()`, etc., to define the time frame for your rule.
2. **Combine rules**: Use multiple rules to create more comprehensive error handling strategies. For example, you might allow a higher number of failures per day, but set a lower threshold for per-minute failures to catch sudden spikes in errors.
3. **Use appropriate intervals**: Choose intervals that make sense for your application. For critical operations, per-minute or per-hour limits might be appropriate, while for less critical operations, per-day limits might suffice.
4. **Consider error severity**: You might create different rules for different types of errors. Critical errors might have lower thresholds than less severe errors.
5. **Use `thenThrow(false)` judiciously**: While `thenThrow(false)` can be useful for monitoring, be cautious about using it for critical errors. It's often better to handle errors actively rather than simply tracking them.
6. **Monitor and adjust**: Regularly review your error handling rules and adjust them based on your application's performance and error patterns.

By effectively using error handling rules, you can create more robust applications that gracefully handle failures and provide better reliability for your users.


# RulePreventsExecutionException

## Overview

`RulePreventsExecutionException` is a custom exception class in the Midnite81\Guardian package. This exception is thrown when a rate limiting rule prevents the execution of a request in the Guardian system.

## Class Details

* **Namespace**: `Midnite81\Guardian\Exceptions`
* **Extends**: `Exception`

## Purpose

The primary purpose of this exception is to provide detailed information when a request is blocked due to rate limiting rules. It encapsulates the specific rule that caused the prevention, allowing for more informative error handling and logging.

## Properties

* `protected ?RateLimitRule $preventingRule`: Stores the rule that prevented the execution. It can be null if no specific rule is identified.

## Constructor

```php
public function __construct(?RateLimitRule $preventingRule = null)
```

The constructor takes an optional `RateLimitRule` object, which represents the rule that prevented the execution.

## Methods

### getPreventingRule

```php
public function getPreventingRule(): ?RateLimitRule
```

Returns the `RateLimitRule` object that prevented the execution, or `null` if no specific rule was identified.

### getErrorMessageFromRule (protected)

```php
protected function getErrorMessageFromRule(): string
```

Generates a human-readable error message based on the preventing rule. This method is used internally to create the exception message.

## Usage

This exception is typically thrown by the Guardian system when a rate limit is exceeded. You can catch this exception to handle rate limiting scenarios in your application.

Example:

```php
use Midnite81\Guardian\Exceptions\RulePreventsExecutionException;

try {
    // Your Guardian-protected code here
} catch (RulePreventsExecutionException $e) {
    $rule = $e->getPreventingRule();
    if ($rule) {
        echo "Request blocked. Rate limit: {$rule->getLimit()} requests per {$rule->getDuration()} {$rule->getInterval()->value}.";
    } else {
        echo "Request blocked by an unspecified rule.";
    }
}
```

## Error Messages

The exception provides detailed error messages:

* If a specific rule prevented execution:

  ```
  Cannot execute the request. Rate limit exceeded: X requests per Y [interval].
  ```

  Where X is the limit, Y is the duration, and \[interval] is the time unit (e.g., second, minute, hour).
* If no specific rule is identified:

  ```
  Cannot execute the request because a rule prevents it.
  ```

## Integration with Guardian

This exception is thrown by the `Guardian::send()` method when `$throwIfRulePrevents` is set to `true` (which is the default behavior). It allows developers to handle rate limiting scenarios gracefully in their applications.

## Best Practices

1. Always catch this exception when working with Guardian-protected code sections.
2. Use the information provided by the exception to inform users about the rate limit and when they can retry.
3. Consider implementing a backoff strategy or queue system for requests that hit rate limits frequently.

By utilizing `RulePreventsExecutionException`, you can create more robust and user-friendly applications that gracefully handle rate limiting scenarios.


# IdentifierCannotBeEmptyException

## Overview

`IdentifierCannotBeEmptyException` is a custom exception class in the Midnite81\Guardian package. This exception is thrown when an attempt is made to set an empty identifier in the Guardian system.

## Class Details

* **Namespace**: `Midnite81\Guardian\Exceptions`
* **Extends**: `Exception`

## Purpose

The primary purpose of this exception is to ensure that valid, non-empty identifiers are always used within the Guardian system. Identifiers are crucial for distinguishing between different rate-limited resources or actions. Guardian does not allow for empty identifiers and will throw this exception.

## Usage

This exception is typically thrown by the Guardian system when setting or updating an identifier. It's most commonly encountered in the `Guardian` class constructor or when using the `setIdentifier` method.

Example of where this exception might be thrown:

```php
use Midnite81\Guardian\Guardian;
use Midnite81\Guardian\Exceptions\IdentifierCannotBeEmptyException;

try {
    $guardian = new Guardian('', $cache);  // This will throw IdentifierCannotBeEmptyException
} catch (IdentifierCannotBeEmptyException $e) {
    echo "Error: " . $e->getMessage();
}
```

## Best Practices

1. Always provide a non-empty string as an identifier when creating a new Guardian instance or setting an identifier.
2. Catch this exception when there's a possibility of receiving an empty identifier from user input or external sources.
3. Provide meaningful identifiers that represent the resource or action being rate-limited.

## Error Message

The default error message for this exception is:

```
Identifier cannot be empty
```

## Handling the Exception

When catching this exception, you should handle it by either:

1. Providing a default identifier
2. Logging the error
3. Notifying the user or system administrator about the invalid input

Example:

```php
use Midnite81\Guardian\Guardian;
use Midnite81\Guardian\Exceptions\IdentifierCannotBeEmptyException;

function createGuardian($identifier, $cache) {
    try {
        return new Guardian($identifier, $cache);
    } catch (IdentifierCannotBeEmptyException $e) {
        // Log the error
        error_log("Attempted to create Guardian with empty identifier: " . $e->getMessage());
        
        // Provide a default identifier
        return new Guardian('default_identifier', $cache);
    }
}
```

## Integration with Guardian

This exception is an integral part of the Guardian system's input validation. It ensures that the system always operates with valid identifiers, which is crucial for maintaining the integrity of rate limiting rules across different resources or actions.


# RateLimitExceededException

## Overview

`RateLimitExceededException` is a custom exception class in the Midnite81\Guardian package. This exception is used to signal that a rate limit has been exceeded, particularly when interacting with external APIs or services within a Guardian-protected context.

## Class Details

* **Namespace**: `Midnite81\Guardian\Exceptions`
* **Extends**: `Exception`

## Purpose

The primary purpose of this exception is to allow developers to integrate external rate limiting information with Guardian's rate limiting system. When thrown inside a closure passed to Guardian's `send()` method, it enables Guardian to manage rate limiting for external services consistently with its internal mechanisms.

## Properties

* `protected DateTimeImmutable $retryAfter`: Stores the time when the client can retry the request.

## Constructor

```php
public function __construct(mixed $retryAfter, string $message = '', int $code = 0, ?Exception $previous = null)
```

The constructor takes a `$retryAfter` parameter, which can be:

* A `DateTimeImmutable` object
* An integer (representing seconds)
* A string (either an HTTP-date or seconds)

## Methods

### getRetryAfter

```php
public function getRetryAfter(): DateTimeImmutable
```

Returns a `DateTimeImmutable` object representing when the client can retry the request.

## Usage

This exception should be thrown by the user inside the closure passed to Guardian's `send()` method, particularly when handling rate limits from external APIs or services.

Example:

```php
use Midnite81\Guardian\Exceptions\RateLimitExceededException;
use GuzzleHttp\Exception\ClientException;

$guardian->send(function () use ($httpClient) {
    try {
        $response = $httpClient->get('https://api.example.com/endpoint');
        return $response->getBody()->getContents();
    } catch (ClientException $e) {
        if ($e->getResponse()->getStatusCode() === 429) {
            $retryAfter = $e->getResponse()->getHeaderLine('Retry-After');
            throw new RateLimitExceededException($retryAfter, 'Rate limit exceeded by external API');
        }
        // Handle other client exceptions...
        throw $e;
    }
});
```

## Integration with Guardian

When this exception is thrown inside the closure passed to `Guardian::send()`, Guardian will catch it and handle the rate limiting accordingly. This allows Guardian to manage rate limits for external services in the same way it manages internal rate limits.

## Best Practices

1. Use this exception to wrap rate limit responses from external APIs or services.
2. Always include the 'Retry-After' information when throwing this exception.
3. Use descriptive error messages to distinguish between different rate limited services or endpoints.
4. Handle other types of exceptions appropriately within the closure.

## Error Messages

The error message for this exception should be descriptive and indicate which service or API triggered the rate limit. For example:

```
Rate limit exceeded by external API
```

## Handling the Exception

Typically, you don't need to catch this exception yourself when using `Guardian::send()`. Guardian will handle it internally. However, if you're using the exception outside of Guardian, you can catch and handle it like this:

```php
try {
    // Your code that might throw RateLimitExceededException
} catch (RateLimitExceededException $e) {
    $retryAfter = $e->getRetryAfter();
    $now = new DateTimeImmutable();
    $waitTime = $retryAfter->getTimestamp() - $now->getTimestamp();
    echo "Rate limit exceeded. Please try again in {$waitTime} seconds.";
}
```

## Automatic Backoff Handling

When this exception is thrown inside a Guardian `send()` method:

1. Guardian will catch the exception and record the rate limit violation.
2. Subsequent calls to `Guardian::send()` for the same identifier will be automatically prevented until the rate limit resets.
3. You don't need to implement additional retry or backoff logic; Guardian handles this for you.

## Security Considerations

By properly using this exception to signal rate limit violations from external services, you ensure that your application respects both internal and external rate limits. This prevents accidental abuse of services and helps maintain good relationships with API providers.

Remember, the key to effective use of `RateLimitExceededException` is throwing it at the appropriate times within Guardian-protected closures, allowing Guardian to manage overall rate limiting behavior for your application.


# StoreException

## Overview

`StoreException` is a base exception class in the Midnite81\Guardian package. This exception serves as the parent class for all storage-related exceptions in the Guardian rate limiting system.

## Class Details

* **Namespace**: `Midnite81\Guardian\Exceptions\Store`
* **Extends**: `Exception`

## Purpose

The primary purpose of this exception is to provide a common base for all storage-related exceptions in the Guardian system. It allows for consistent handling of storage issues regardless of the specific storage implementation being used (e.g., DatabaseStore, RedisStore, FileStore).

## Hierarchy

`StoreException` is the parent class for more specific storage exceptions:

```
Exception
└── StoreException
    ├── DatabaseStoreException
    ├── RedisStoreException
    └── FileStoreException
```

This hierarchy allows for both general and specific exception handling depending on the needs of your application.

## Usage

While you typically won't throw `StoreException` directly, you might catch it to handle any storage-related exception in a general way:

```php
use Midnite81\Guardian\Exceptions\Store\StoreException;

try {
    // Your Guardian code using any storage method
    $guardian->send(function() {
        // Your rate-limited code here
    });
} catch (StoreException $e) {
    // Handle any storage-related exception
    error_log("Guardian storage error: " . $e->getMessage());
    // Implement fallback behavior
}
```

## Best Practices

1. Use `StoreException` for catch-all handling of storage issues when you don't need to distinguish between specific storage types.
2. Implement more specific exception handling (e.g., `DatabaseStoreException`) when you need to react differently based on the storage type.
3. Always log the full exception details for debugging purposes.
4. Consider implementing a fallback mechanism or graceful degradation when any storage exception occurs.

## Error Messages

The error messages for this exception will vary depending on the specific subclass that is thrown. However, all will relate to storage operations within the Guardian system.

## Integration with Guardian

`StoreException` is fundamental to Guardian's error handling system for storage operations. It provides a consistent way to catch and handle any storage-related issues, regardless of the specific storage implementation being used.

## Extending StoreException

If you're implementing a custom storage solution for Guardian, you should create a custom exception that extends `StoreException`. For example:

```php
use Midnite81\Guardian\Exceptions\Store\StoreException;

class CustomStoreException extends StoreException
{
    // Add any custom functionality here
}
```

## Security Considerations

When handling `StoreException` or its subclasses, be careful not to expose sensitive information about your storage system in user-facing error messages. Always log the full exception details securely, but provide only general error messages to end-users.

## Troubleshooting

If you're frequently encountering `StoreException` or its subclasses:

1. Review your storage configuration (database settings, Redis connection, file permissions, etc.).
2. Ensure your chosen storage system is properly set up and accessible.
3. Check for common issues like disk space, connection limits, or permission problems.
4. Consider implementing a monitoring system to alert you of persistent storage issues.

## Performance Implications

While `StoreException` itself doesn't directly impact performance, frequent storage exceptions can significantly affect your application's performance and reliability. Monitor the frequency of these exceptions and address underlying issues promptly.

By properly handling `StoreException` and its subclasses, you can ensure that your application gracefully manages storage-related issues in the Guardian rate limiting system, maintaining reliability across different storage implementations. This base exception class provides a flexible foundation for robust error handling in Guardian's storage operations.


# DatabaseStoreException

## Overview

`DatabaseStoreException` is a custom exception class in the Midnite81\Guardian package. This exception is thrown when there are issues related to database operations within the `DatabaseStore` class, which is one of the storage options for the Guardian rate limiting system.

## Class Details

* **Namespace**: `Midnite81\Guardian\Exceptions\Store`
* **Extends**: `StoreException`

## Purpose

The primary purpose of this exception is to provide specific error handling for database-related issues that may occur when Guardian is using a database for storing rate limiting data. It helps distinguish database-specific errors from other types of storage errors in the Guardian system.

## Usage

This exception is typically thrown by the `DatabaseStore` class when it encounters issues with database operations such as:

* Failed connections
* Query execution errors
* Data integrity issues

Example of where this exception might be thrown (inside `DatabaseStore`):

```php
try {
    $stmt = $this->pdo->prepare("SELECT * FROM {$this->tableName} WHERE `key` = :key");
    $stmt->execute(['key' => $key]);
    // ... more code ...
} catch (PDOException $e) {
    throw new DatabaseStoreException($e->getMessage(), $e->getCode(), $e);
}
```

## Catching and Handling

When using Guardian with a database store, you should be prepared to catch and handle this exception:

```php
use Midnite81\Guardian\Exceptions\Store\DatabaseStoreException;

try {
    // Your Guardian code using DatabaseStore
    $guardian->send(function() {
        // Your rate-limited code here
    });
} catch (DatabaseStoreException $e) {
    // Log the error
    error_log("Database error in Guardian: " . $e->getMessage());
    
    // Gracefully degrade or use a fallback mechanism
    // For example, you might choose to allow the request in case of storage errors
    return true;
}
```

## Best Practices

1. Always catch this exception when using Guardian with a `DatabaseStore`.
2. Log the exception details for debugging and monitoring purposes.
3. Implement appropriate fallback mechanisms or graceful degradation when database operations fail.
4. Consider using a different storage mechanism (like Redis or file-based storage) if database issues are frequent.

## Error Messages

The error messages for this exception will typically include details about the specific database operation that failed. These can include:

* SQL query errors
* Connection issues
* Constraint violations

For example:

```
SQLSTATE[HY000]: General error: 1 no such table: guardian_cache
```

## Integration with Guardian

This exception is an integral part of Guardian's error handling system for database storage. It allows the system to differentiate between different types of storage errors and provide more specific error handling and reporting.

## Security Considerations

When handling `DatabaseStoreException`, be careful not to expose sensitive database information in user-facing error messages. Always log the full exception details securely, but provide only general error messages to end-users.

## Troubleshooting

If you frequently encounter `DatabaseStoreException`:

1. Check your database connection settings.
2. Ensure the required tables are properly set up (Guardian should create these automatically, but check if there are permission issues).
3. Verify that your database user has the necessary permissions for the operations Guardian is attempting.
4. Consider increasing your database's max\_connections if you're hitting connection limits.

By properly handling `DatabaseStoreException`, you can ensure that your application gracefully manages database-related issues in the Guardian rate limiting system, maintaining reliability and performance even when database operations fail.


# FileStoreException

## Overview

`FileStoreException` is a custom exception class in the Midnite81\Guardian package. This exception is thrown when there are issues related to file operations within the `FileStore` class, which is one of the storage options for the Guardian rate limiting system.

## Class Details

* **Namespace**: `Midnite81\Guardian\Exceptions\Store`
* **Extends**: `StoreException`

## Purpose

The primary purpose of this exception is to provide specific error handling for file-related issues that may occur when Guardian is using the filesystem for storing rate limiting data. It helps distinguish file-specific errors from other types of storage errors in the Guardian system.

## Usage

This exception is typically thrown by the `FileStore` class when it encounters issues with file operations such as:

* Failed file creation or deletion
* Permission issues
* Disk space problems
* JSON encoding or decoding errors

Example of where this exception might be thrown (inside `FileStore`):

```php
try {
    $jsonData = json_encode($data, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    throw new FileStoreException($e->getMessage(), $e->getCode(), $e);
}

$result = $this->system->filePutContents($filename, $jsonData);
if ($result === false) {
    throw new FileStoreException("Failed to write cache file: $filename");
}
```

## Catching and Handling

When using Guardian with a file store, you should be prepared to catch and handle this exception:

```php
use Midnite81\Guardian\Exceptions\Store\FileStoreException;

try {
    // Your Guardian code using FileStore
    $guardian->send(function() {
        // Your rate-limited code here
    });
} catch (FileStoreException $e) {
    // Log the error
    error_log("File storage error in Guardian: " . $e->getMessage());
    
    // Gracefully degrade or use a fallback mechanism
    // For example, you might choose to allow the request in case of storage errors
    return true;
}
```

## Best Practices

1. Always catch this exception when using Guardian with a `FileStore`.
2. Log the exception details for debugging and monitoring purposes.
3. Implement appropriate fallback mechanisms or graceful degradation when file operations fail.
4. Regularly check and manage disk space to prevent storage-related issues.
5. Ensure proper file permissions are set for the directory used by FileStore.

## Error Messages

The error messages for this exception will typically include details about the specific file operation that failed. These can include:

* File write or read failures
* Directory creation issues
* JSON parsing errors

For example:

```
Failed to write cache file: /path/to/cache/file.json
```

or

```
Failed to create cache directory: /path/to/cache
```

## Integration with Guardian

This exception is an integral part of Guardian's error handling system for file-based storage. It allows the system to differentiate between different types of storage errors and provide more specific error handling and reporting.

## Security Considerations

When handling `FileStoreException`, be careful not to expose sensitive file system information or paths in user-facing error messages. Always log the full exception details securely, but provide only general error messages to end-users.

## Troubleshooting

If you frequently encounter `FileStoreException`:

1. Check file and directory permissions for the cache storage location.
2. Ensure there's sufficient disk space available.
3. Verify that the PHP process has write access to the cache directory.
4. Check for any file locking issues that might prevent writing or reading.
5. Consider using a different storage mechanism (like Redis or database storage) if file system issues persist.

## Performance Implications

While file-based storage can be effective, it may not be as performant as in-memory solutions like Redis for high-traffic applications. Monitor the frequency of file operations and consider alternative storage methods if performance becomes an issue.

By properly handling `FileStoreException`, you can ensure that your application gracefully manages file-related issues in the Guardian rate limiting system, maintaining reliability even when file operations fail. This exception helps in identifying and addressing filesystem-related problems quickly, ensuring the stability of your rate limiting implementation.


# RedisStoreException

## Overview

`RedisStoreException` is a custom exception class in the Midnite81\Guardian package. This exception is thrown when there are issues related to Redis operations within the `RedisStore` class, which is one of the storage options for the Guardian rate limiting system.

## Class Details

* **Namespace**: `Midnite81\Guardian\Exceptions\Store`
* **Extends**: `StoreException`

## Purpose

The primary purpose of this exception is to provide specific error handling for Redis-related issues that may occur when Guardian is using Redis for storing rate limiting data. It helps distinguish Redis-specific errors from other types of storage errors in the Guardian system.

## Usage

This exception is typically thrown by the `RedisStore` class when it encounters issues with Redis operations such as:

* Connection failures
* Command execution errors
* Serialization or deserialization issues

Example of where this exception might be thrown (inside `RedisStore`):

```php
try {
    $value = $this->redis->get($this->prefix . $key);
    if ($value === false) {
        return $default;
    }
    $result = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
    return $result ?? $default;
} catch (RedisException|JsonException $e) {
    throw new RedisStoreException($e->getMessage(), $e->getCode(), $e);
}
```

## Catching and Handling

When using Guardian with a Redis store, you should be prepared to catch and handle this exception:

```php
use Midnite81\Guardian\Exceptions\Store\RedisStoreException;

try {
    // Your Guardian code using RedisStore
    $guardian->send(function() {
        // Your rate-limited code here
    });
} catch (RedisStoreException $e) {
    // Log the error
    error_log("Redis error in Guardian: " . $e->getMessage());
    
    // Gracefully degrade or use a fallback mechanism
    // For example, you might choose to allow the request in case of storage errors
    return true;
}
```

## Best Practices

1. Always catch this exception when using Guardian with a `RedisStore`.
2. Log the exception details for debugging and monitoring purposes.
3. Implement appropriate fallback mechanisms or graceful degradation when Redis operations fail.
4. Consider using a different storage mechanism (like database or file-based storage) if Redis issues are frequent.
5. Implement retry logic for transient Redis errors, if appropriate for your use case.

## Error Messages

The error messages for this exception will typically include details about the specific Redis operation that failed. These can include:

* Connection errors
* Timeout issues
* Command execution failures

For example:

```
Redis connection timed out
```

or

```
Redis command execution failed: WRONGTYPE Operation against a key holding the wrong kind of value
```

## Integration with Guardian

This exception is an integral part of Guardian's error handling system for Redis storage. It allows the system to differentiate between different types of storage errors and provide more specific error handling and reporting.

## Security Considerations

When handling `RedisStoreException`, be careful not to expose sensitive Redis server information in user-facing error messages. Always log the full exception details securely, but provide only general error messages to end-users.

## Troubleshooting

If you frequently encounter `RedisStoreException`:

1. Check your Redis server status and connection settings.
2. Ensure that your Redis server has enough memory allocated.
3. Verify that the Redis commands being used are supported by your Redis version.
4. Check for network issues between your application and the Redis server.
5. Consider implementing a Redis sentinel or cluster for improved reliability.

## Performance Implications

While Redis is generally very fast, frequent `RedisStoreException` occurrences could impact the performance of your rate limiting system. Monitor these exceptions closely and consider adjusting your Redis configuration or scaling your Redis infrastructure if needed.

By properly handling `RedisStoreException`, you can ensure that your application gracefully manages Redis-related issues in the Guardian rate limiting system, maintaining reliability and performance even when Redis operations fail.


