From 99e34d1d06d49506bcd645ddc6ec5b11a3240006 Mon Sep 17 00:00:00 2001 From: Fabio Ivona Date: Fri, 19 Apr 2024 16:44:32 +0200 Subject: [PATCH] v2 --- composer.json | 30 +++-- src/Concerns/DefinesFeatures.php | 192 ++++++++++++++++++++++++++-- src/Exceptions/FeatureException.php | 9 +- src/ServiceProvider.php | 2 +- tests/ArchTest.php | 5 - tests/BaseTest.php | 23 ---- tests/CustomConfigTest.php | 23 ---- tests/Fixtures/CustomFeature.php | 18 --- tests/Fixtures/Feature.php | 14 -- tests/TestCase.php | 13 -- 10 files changed, 201 insertions(+), 128 deletions(-) delete mode 100644 tests/ArchTest.php delete mode 100644 tests/BaseTest.php delete mode 100644 tests/CustomConfigTest.php delete mode 100644 tests/Fixtures/CustomFeature.php delete mode 100644 tests/Fixtures/Feature.php diff --git a/composer.json b/composer.json index 3131f60..4d5d8b1 100644 --- a/composer.json +++ b/composer.json @@ -1,12 +1,13 @@ { "name": "defstudio/enum-features", - "description": "A simple trait to enable a feature system using Enums", + "description": "A simple trait to enable a feature system using Enums and Laravel Pennant", "keywords": [ "defstudio", "laravel", + "pennant", "enum-features", "enums", - "feature" + "features" ], "homepage": "https://github.com/defstudio/enum-features", "license": "MIT", @@ -18,20 +19,21 @@ } ], "require": { - "php": "^8.1", - "spatie/laravel-package-tools": "^1.14.0" + "php": "^8.2", + "spatie/laravel-package-tools": "^1.16.4", + "illuminate/support": "^v11.4.0", + "laravel/pennant": "^v1.7.0" }, "require-dev": { - "laravel/pint": "^1.0", - "nunomaduro/collision": "^7.9", - "nunomaduro/larastan": "^2.0.1", - "orchestra/testbench": "^8.0", - "pestphp/pest": "^2.0", - "pestphp/pest-plugin-arch": "^2.0", - "pestphp/pest-plugin-laravel": "^2.0", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.0" + "laravel/pint": "^v1.15.1", + "nunomaduro/collision": "^v8.1.1", + "larastan/larastan": "^v2.9.5", + "orchestra/testbench": "^v9.0.4", + "pestphp/pest": "^v2.34.7", + "pestphp/pest-plugin-laravel": "^v2.3.0", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan-deprecation-rules": "^1.1.4", + "phpstan/phpstan-phpunit": "^1.3.16" }, "autoload": { "psr-4": { diff --git a/src/Concerns/DefinesFeatures.php b/src/Concerns/DefinesFeatures.php index e1412bf..f209120 100644 --- a/src/Concerns/DefinesFeatures.php +++ b/src/Concerns/DefinesFeatures.php @@ -6,27 +6,201 @@ namespace DefStudio\EnumFeatures\Concerns; -use DefStudio\EnumFeatures\Exceptions\FeatureException; +use BackedEnum; +use Illuminate\Contracts\Auth\Authenticatable; +use Laravel\Pennant\Feature as Pennant; +use Laravel\Pennant\Middleware\EnsureFeaturesAreActive; trait DefinesFeatures { - public static function enabledFeatures(): array + public function define(): void { - return config('app.features', []); + Pennant::define($this->featureName(), $this->resolve(...)); } - public function enabled(): bool + protected function featureName(): string { - return in_array($this, self::enabledFeatures()); + return $this instanceof BackedEnum ? $this->value : $this->name; } - public function enforce(): void + public function active(?Authenticatable $scope = null): bool { - throw_if(! $this->enabled(), FeatureException::notEnabled($this)); + if ($scope) { + return Pennant::for($scope)->active($this->featureName()); + } + + return Pennant::active($this->featureName()); + } + + public function enabled(?Authenticatable $scope = null): bool + { + return $this->active($scope); + } + + public function inactive(?Authenticatable $scope = null): bool + { + return ! $this->active($scope); + } + + public function disabled(?Authenticatable $scope = null): bool + { + return $this->inactive($scope); + } + + public function middleware(): string + { + return EnsureFeaturesAreActive::using($this->featureName()); + } + + public function purge(): void + { + Pennant::purge($this->featureName()); + } + + protected function resolve(?Authenticatable $scope = null): bool + { + $featureName = $this->featureName(); + $camelFeatureName = str($this->featureName())->camel()->ucfirst(); + + $try_methods = [ + "check_$featureName", + "check_{$featureName}_feature", + "has_$featureName", + "has_{$featureName}Feature", + "check$camelFeatureName", + "check{$camelFeatureName}Feature", + "has$camelFeatureName", + "has{$camelFeatureName}Feature", + ]; + + foreach ($try_methods as $method) { + if (method_exists($this, $method)) { + return $this->{$method}($scope); + } + } + + return false; + } + + public function enforce(?Authenticatable $scope = null): void + { + if (! $this->active($scope)) { + abort(400); + } + } + + public function activate(?Authenticatable $scope = null): void + { + if ($scope) { + Pennant::for($scope)->activate($this->featureName()); + + return; + } + + Pennant::activate($this->featureName()); + } + + public function enable(?Authenticatable $scope = null): void + { + $this->activate($scope); + } + + public function deactivate(?Authenticatable $scope = null): void + { + if ($scope) { + Pennant::for($scope)->deactivate($this->featureName()); + + return; + } + + Pennant::deactivate($this->featureName()); + } + + public function disable(?Authenticatable $scope = null): void + { + $this->deactivate($scope); + } + + public function forget(?Authenticatable $scope = null): void + { + if ($scope) { + Pennant::for($scope)->forget($this->featureName()); + + return; + } + + Pennant::forget($this->featureName()); + } + + /** + * @param array $features + */ + public static function areAllActive(array $features): bool + { + return Pennant::allAreInactive(collect($features) + ->map(fn (self $feature) => $feature->featureName()) + ->toArray()); + } + + /** + * @param array $features + */ + public static function someAreActive(array $features): bool + { + return Pennant::someAreActive(collect($features) + ->map(fn (self $feature) => $feature->featureName()) + ->toArray()); + } + + /** + * @param array $features + */ + public static function areAllEnabled(array $features): bool + { + return self::areAllActive($features); + } + + /** + * @param array $features + */ + public static function someAreEnabled(array $features): bool + { + return self::someAreActive($features); + } + + /** + * @param array $features + */ + public static function areAllInactive(array $features): bool + { + return Pennant::allAreInactive(collect($features) + ->map(fn (self $feature) => $feature->featureName()) + ->toArray()); + } + + /** + * @param array $features + */ + public static function someAreInactive(array $features): bool + { + return Pennant::someAreInactive(collect($features) + ->map(fn (self $feature) => $feature->featureName()) + ->toArray()); + } + + /** + * @param array $features + */ + public static function areAllDisabled(array $features): bool + { + return self::areAllInactive($features); } - public function disabled(): bool + /** + * @param array $features + */ + public static function someAreDisabled(array $features): bool { - return ! $this->enabled(); + return self::someAreInactive($features); } } diff --git a/src/Exceptions/FeatureException.php b/src/Exceptions/FeatureException.php index e8198cd..9b00b23 100644 --- a/src/Exceptions/FeatureException.php +++ b/src/Exceptions/FeatureException.php @@ -8,14 +8,7 @@ class FeatureException extends Exception { - public static function notEnabled(UnitEnum $feature): FeatureException - { - $name = $feature instanceof BackedEnum ? $feature->value : $feature->name; - - return new self("Feature [$name] is not enabled"); - } - - public static function invalid_feature_enum(UnitEnum $feature): FeatureException + public static function invalid_feature(UnitEnum $feature): FeatureException { $name = $feature instanceof BackedEnum ? $feature->value : $feature->name; diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 777dc28..5a582b8 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -20,7 +20,7 @@ public function packageBooted(): void { Blade::if('feature', function (UnitEnum $feature) { if (! class_uses($feature, DefinesFeatures::class)) { - throw FeatureException::invalid_feature_enum($feature); + throw FeatureException::invalid_feature($feature); } /** @var DefinesFeatures $feature */ diff --git a/tests/ArchTest.php b/tests/ArchTest.php deleted file mode 100644 index ccc19b2..0000000 --- a/tests/ArchTest.php +++ /dev/null @@ -1,5 +0,0 @@ -expect(['dd', 'dump', 'ray']) - ->each->not->toBeUsed(); diff --git a/tests/BaseTest.php b/tests/BaseTest.php deleted file mode 100644 index 660a236..0000000 --- a/tests/BaseTest.php +++ /dev/null @@ -1,23 +0,0 @@ -enabled())->toBeTrue(); - - expect(Feature::other_feature->enabled())->toBeFalse(); -}); - -it('can check if a feature is not enabled', function () { - expect(Feature::welcome_email->disabled())->toBeFalse(); - - expect(Feature::other_feature->disabled())->toBeTrue(); -}); - -it('can enforce a feature to be enabled', function () { - Feature::welcome_email->enforce(); - - expect(fn () => Feature::other_feature->enforce()) - ->toThrow(FeatureException::class, 'Feature [other_feature] is not enabled'); -}); diff --git a/tests/CustomConfigTest.php b/tests/CustomConfigTest.php deleted file mode 100644 index 9c4f049..0000000 --- a/tests/CustomConfigTest.php +++ /dev/null @@ -1,23 +0,0 @@ -enabled())->toBeTrue(); - - expect(CustomFeature::payments->enabled())->toBeFalse(); -}); - -it('can check if a feature is not enabled', function () { - expect(CustomFeature::guest_account->disabled())->toBeFalse(); - - expect(CustomFeature::payments->disabled())->toBeTrue(); -}); - -it('can enforce a feature to be enabled', function () { - CustomFeature::guest_account->enforce(); - - expect(fn () => CustomFeature::payments->enforce()) - ->toThrow(FeatureException::class, 'Feature [payments] is not enabled'); -}); diff --git a/tests/Fixtures/CustomFeature.php b/tests/Fixtures/CustomFeature.php deleted file mode 100644 index 42b2e5e..0000000 --- a/tests/Fixtures/CustomFeature.php +++ /dev/null @@ -1,18 +0,0 @@ -set('app.features', [ - Feature::multi_language, - Feature::welcome_email, - ]); - - config()->set('my_package.enabled_features', [ - CustomFeature::guest_account, - ]); - } }