diff --git a/README.md b/README.md index e628789..78a957d 100644 --- a/README.md +++ b/README.md @@ -386,6 +386,65 @@ class LaravelCacheStore implements StoreInterface } ``` +## Framework Integration + +### Custom Resolver + +For framework adapters (like Laravel, Symfony, etc.), you can override how `Cloak::make()` resolves instances using `resolveUsing()`: + +```php +use DynamikDev\Cloak\Cloak; + +// Set a custom resolver (typically in a service provider) +Cloak::resolveUsing(fn() => app(Cloak::class)); + +// Now Cloak::make() resolves from your container +$cloak = Cloak::make(); // Uses your container binding +``` + +This allows framework packages to: +- Integrate with dependency injection containers +- Use framework-specific storage drivers +- Apply framework configuration automatically +- Let developers extend and customize via container bindings + +**Example Laravel Service Provider:** + +```php +use DynamikDev\Cloak\Cloak; +use Illuminate\Support\ServiceProvider; + +class CloakServiceProvider extends ServiceProvider +{ + public function register() + { + // Bind Cloak to the container with your configuration + $this->app->bind(Cloak::class, function ($app) { + return Cloak::using($app->make(CacheStore::class)) + ->withDetectors(config('cloak.detectors', Detector::all())); + }); + + // Make helpers use container resolution + Cloak::resolveUsing(fn() => app(Cloak::class)); + } +} +``` + +Now developers can customize behavior through container bindings: + +```php +// In AppServiceProvider +$this->app->bind(Cloak::class, function ($app) { + return CustomCloak::using($app->make(CacheStore::class)); +}); +``` + +**Clearing the Resolver:** + +```php +Cloak::clearResolver(); // Reverts to default behavior +``` + ## Extending Cloak Cloak follows a compositional architecture, making it easy to extend with custom implementations. diff --git a/src/Cloak.php b/src/Cloak.php index fd2bab0..9cfe44d 100644 --- a/src/Cloak.php +++ b/src/Cloak.php @@ -22,6 +22,9 @@ class Cloak use HasLifecycleCallbacks; use ManagesStorage; + /** @var (callable(): self)|null */ + protected static $resolver = null; + /** @var array|null */ protected ?array $defaultDetectors = null; @@ -42,9 +45,32 @@ public static function using(StoreInterface $store): self public static function make(?StoreInterface $store = null): self { + if (self::$resolver !== null) { + return (self::$resolver)(); + } + return new self($store ?? self::getDefaultStore()); } + /** + * Set a custom resolver for creating Cloak instances. + * This allows framework adapters to override the default factory behavior. + * + * @param callable(): self $resolver + */ + public static function resolveUsing(callable $resolver): void + { + self::$resolver = $resolver; + } + + /** + * Clear the custom resolver, reverting to default factory behavior. + */ + public static function clearResolver(): void + { + self::$resolver = null; + } + /** * Set the default detectors to use when none are specified. * diff --git a/tests/CloakTest.php b/tests/CloakTest.php index 2cf621d..5a9820c 100644 --- a/tests/CloakTest.php +++ b/tests/CloakTest.php @@ -160,3 +160,51 @@ expect($result)->toMatch('/Card: \{\{CREDIT_CARD_[a-zA-Z0-9]{6}_1\}\}/'); }); + +it('uses custom resolver when set', function () { + $customStore = new ArrayStore(); + $customCloak = Cloak::using($customStore); + + Cloak::resolveUsing(fn() => $customCloak); + + $resolved = Cloak::make(); + + expect($resolved)->toBe($customCloak); + + Cloak::clearResolver(); +}); + +it('resolver is called on every make() call', function () { + $callCount = 0; + + Cloak::resolveUsing(function () use (&$callCount) { + $callCount++; + return Cloak::using(new ArrayStore()); + }); + + Cloak::make(); + Cloak::make(); + Cloak::make(); + + expect($callCount)->toBe(3); + + Cloak::clearResolver(); +}); + +it('clearResolver reverts to default behavior', function () { + $customStore = new ArrayStore(); + Cloak::resolveUsing(fn() => Cloak::using($customStore)); + + Cloak::clearResolver(); + + $resolved = Cloak::make(); + + expect($resolved)->toBeInstanceOf(Cloak::class); + expect($resolved)->not->toBe(Cloak::using($customStore)); +}); + +it('make uses default store when no resolver set', function () { + $cloak = Cloak::make(); + + expect($cloak)->toBeInstanceOf(Cloak::class); +});