diff --git a/composer.json b/composer.json index 36d8879..4ead807 100644 --- a/composer.json +++ b/composer.json @@ -26,17 +26,18 @@ "toggles" ], "require": { - "php": "^7.1.3" + "php": "^7.1.3" }, "require-dev": { + "doctrine/dbal": "2.9.3", "illuminate/support": "^5.6", "mockery/mockery": "0.9.*", + "orchestra/database": "^3.6.0", "orchestra/testbench": "^3.6", + "php-coveralls/php-coveralls": "^2.1", "phpunit/phpunit": "^7.0", "ramsey/uuid": "^3.0", - "orchestra/database": "^3.6.0", - "squizlabs/php_codesniffer": "^2.3", - "php-coveralls/php-coveralls": "^2.1" + "squizlabs/php_codesniffer": "^2.3" }, "autoload": { "psr-4": { diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 8a596d6..011b9a5 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -14,6 +14,7 @@ return [ 'name' => $faker->word, 'email' => $faker->email, - 'password' => bcrypt(str_random(25)) + 'password' => bcrypt(str_random(25)), + 'roles' => "['admin']" ]; }); diff --git a/readme.md b/readme.md index 4072516..fdf521c 100644 --- a/readme.md +++ b/readme.md @@ -129,6 +129,26 @@ if(\FriendsOfCat\LaravelFeatureFlags\Feature::exists('see-twitter-field')) } ~~~ +### Enable for User Roles +You can enable a feature flag for specific user roles, by using the **roles** variant in the configuration form + +i.e. + +~~~ +{ "roles": ["admin", "dev"]} +~~~ + +If you don't have a roles property in your User model, you just need to implement the **FeatureFlagsEnabler** Interface and use **FeatureFlagUserRoleTrait** + +~~~ +use FriendsOfCat\LaravelFeatureFlags\FeatureFlagsEnabler; +use FriendsOfCat\LaravelFeatureFlags\FeatureFlagUserRoleTrait; + +class User extends Authenticatable implements FeatureFlagsUserRoles +{ + use AuthenticableTrait, FeatureFlagUserRoleTrait; +} +~~~ ## Usage Non Auth diff --git a/src/Feature.php b/src/Feature.php index 9ff5c96..8a093cb 100644 --- a/src/Feature.php +++ b/src/Feature.php @@ -2,8 +2,10 @@ namespace FriendsOfCat\LaravelFeatureFlags; +use Illuminate\Support\Arr; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Cache; +use FriendsOfCat\LaravelFeatureFlags\FeatureFlagsEnabler; class Feature { @@ -48,7 +50,7 @@ public function isEnabled($featureKey, $variant = null, $user = null) } if ($variant != self::ON and $variant != self::OFF) { - return $this->isUserEnabled($variant, $user); + return $this->isUserEnabled($variant, $user) || $this->isRoleEnabled($variant, $user); } return $variant == self::ON; @@ -95,7 +97,6 @@ protected function isUserEnabled($feature_variant, $user = null) return false; } - /** * @param \Illuminate\Contracts\Auth\Access\Authorizable $user (optional) * @return string @@ -112,4 +113,36 @@ public function getUserEmail($user) return Auth::user()->email; } + + private function getUserRoles($user) + { + return ($user && $user->roles) ? $user->roles : false; + } + + public function isRoleEnabled($feature_variant, $user = null) + { + $fieldName = 'roles'; + + if (empty($feature_variant[$fieldName])) { + return false; + } + + $user = $user ?? Auth::user(); + if ($user_roles = + ($user instanceof FeatureFlagsEnabler) + ? $user->getFieldValueForFeatureFlags($fieldName) + : $this->getUserRoles($user) + ) { + $filtered = Arr::where( + array_map('strtolower', $user_roles), + function ($value, $key) use ($feature_variant, $fieldName) { + return in_array($value, $feature_variant[$fieldName], true); + } + ); + + return ! empty($filtered); + } + + return false; + } } diff --git a/src/FeatureFlagHelper.php b/src/FeatureFlagHelper.php index 6bbb856..fa6984e 100644 --- a/src/FeatureFlagHelper.php +++ b/src/FeatureFlagHelper.php @@ -46,6 +46,10 @@ private function transformFeatures($features, $value, $key) $features[$value['key']]['users'] = $value['variants']['users']; } + if (isset($value['variants']['roles'])) { + $features[$value['key']]['roles'] = $value['variants']['roles']; + } + return $features; } diff --git a/src/FeatureFlagUserRoleTrait.php b/src/FeatureFlagUserRoleTrait.php new file mode 100644 index 0000000..f6f2035 --- /dev/null +++ b/src/FeatureFlagUserRoleTrait.php @@ -0,0 +1,11 @@ +$fieldName, true); + } +} diff --git a/src/FeatureFlagsEnabler.php b/src/FeatureFlagsEnabler.php new file mode 100644 index 0000000..f9edf22 --- /dev/null +++ b/src/FeatureFlagsEnabler.php @@ -0,0 +1,8 @@ +assertFalse($this->app->get(Gate::class)->allows('feature-flag', 'testing')); } + public function testOnForUserRole() + { + $this->user = factory(FeatureFlagUser::class)->create(['email' => 'foo2@gmail.com']); + $this->user->setRawAttributes(['roles' => ['admin', 'editor']]); + + $this->be($this->user); + + factory(FeatureFlag::class)->create( + [ + 'key' => 'testing', + 'variants' => [ + 'roles' => [ + 'admin' + ] + ] + ] + ); + + $this->registerFeatureFlags(); + + $this->assertTrue($this->app->get(Gate::class)->allows('feature-flag', 'testing')); + } + + public function testOffForUserRole() + { + $this->user = factory(FeatureFlagUser::class)->create(['email' => 'foo2@gmail.com']); + $this->user->setRawAttributes(['roles' => ['editor']]); + + $this->be($this->user); + + factory(FeatureFlag::class)->create( + [ + 'key' => 'testing', + 'variants' => [ + 'roles' => [ + 'admin', 'manager' + ] + ] + ] + ); + + $this->registerFeatureFlags(); + + $this->assertFalse($this->app->get(Gate::class)->allows('feature-flag', 'testing')); + } } diff --git a/tests/FeatureFlagsEnablerTest.php b/tests/FeatureFlagsEnablerTest.php new file mode 100644 index 0000000..b0ffd55 --- /dev/null +++ b/tests/FeatureFlagsEnablerTest.php @@ -0,0 +1,61 @@ + $this->faker->word, + 'email' => $this->faker->email, + 'password' => bcrypt(str_random(25)), + 'roles' => json_encode($userRoles) + ]); + $user->save(); + + $this->be($user); + + factory(FeatureFlag::class)->create( + [ + 'key' => 'testing', + 'variants' => [ + 'roles' => [ + 'developer', 'admin' + ] + ] + ] + ); + + $this->registerFeatureFlags(); + + $this->assertTrue($this->app->get(Gate::class)->allows('feature-flag', 'testing')); + } + + public function validData() + { + return [ + [ + 'admin' + ], + [ + ['admin', 'developer'] + ], + [ + 'developer' + ] + ]; + } +} diff --git a/tests/fixtures/UserWithFeatureFlagsEnablerInterface.php b/tests/fixtures/UserWithFeatureFlagsEnablerInterface.php new file mode 100644 index 0000000..5ebaf6e --- /dev/null +++ b/tests/fixtures/UserWithFeatureFlagsEnablerInterface.php @@ -0,0 +1,22 @@ +string('roles')->nullable(); + }); + } + + public function down() + { + if (Schema::hasColumn('users', 'roles')) { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('roles'); + }); + } + } +} diff --git a/views/form.blade.php b/views/form.blade.php index 989c7f4..421ec37 100644 --- a/views/form.blade.php +++ b/views/form.blade.php @@ -21,6 +21,12 @@
{ "users": [ "foo@gmail.com" ] }
or
+
{ "roles": [ "developer" ] }
+ or +
+
{ "roles": [ "developer" ], "users": [ "foo@bar.com" ] }
+ or +
"off"