Skip to content

Commit

Permalink
Allow control over feature serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
timacdonald committed Nov 6, 2024
1 parent 4580a04 commit 4b3e6c1
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 13 deletions.
11 changes: 11 additions & 0 deletions src/Contracts/FeatureScopeSerializeable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Laravel\Pennant\Contracts;

interface FeatureScopeSerializeable
{
/**
* Serialize the feature scope for storage.
*/
public function featureScopeSerialize(string $driver): string;
}
8 changes: 4 additions & 4 deletions src/Drivers/Decorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ public function get($feature, $scope): mixed
$scope = $this->resolveScope($scope);

$item = $this->cache
->whereStrict('scope', Feature::serializeScope($scope))
->whereStrict('scope', Feature::serializeScope($scope, $this->name))
->whereStrict('feature', $feature)
->first();

Expand Down Expand Up @@ -585,7 +585,7 @@ protected function resolveScope($scope)
*/
protected function isCached($feature, $scope)
{
$scope = Feature::serializeScope($scope);
$scope = Feature::serializeScope($scope, $this->name, $this->name, $this->name, $this->name, $this->name, $this->name, $this->name, $this->name);

return $this->cache->search(
fn ($item) => $item['feature'] === $feature && $item['scope'] === $scope
Expand All @@ -602,7 +602,7 @@ protected function isCached($feature, $scope)
*/
protected function putInCache($feature, $scope, $value)
{
$scope = Feature::serializeScope($scope);
$scope = Feature::serializeScope($scope, $this->name);

$position = $this->cache->search(
fn ($item) => $item['feature'] === $feature && $item['scope'] === $scope
Expand All @@ -624,7 +624,7 @@ protected function putInCache($feature, $scope, $value)
*/
protected function removeFromCache($feature, $scope)
{
$scope = Feature::serializeScope($scope);
$scope = Feature::serializeScope($scope, $this->name);

$position = $this->cache->search(
fn ($item) => $item['feature'] === $feature && $item['scope'] === $scope
Expand Down
7 changes: 5 additions & 2 deletions src/FeatureManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use InvalidArgumentException;
use Laravel\Pennant\Contracts\FeatureScopeSerializeable;
use Laravel\Pennant\Drivers\ArrayDriver;
use Laravel\Pennant\Drivers\DatabaseDriver;
use Laravel\Pennant\Drivers\Decorator;
Expand Down Expand Up @@ -178,17 +179,19 @@ public function createDatabaseDriver(array $config, string $name)
* Serialize the given scope for storage.
*
* @param mixed $scope
* @param string $driver
* @return string|null
*/
public function serializeScope($scope)
public function serializeScope($scope, $driver = '')
{
return match (true) {
$scope instanceof FeatureScopeSerializeable => $scope->featureScopeSerialize($driver),
$scope === null => '__laravel_null',
is_string($scope) => $scope,
is_numeric($scope) => (string) $scope,
$scope instanceof Model && $this->useMorphMap => $scope->getMorphClass().'|'.$scope->getKey(),
$scope instanceof Model && ! $this->useMorphMap => $scope::class.'|'.$scope->getKey(),
default => throw new RuntimeException('Unable to serialize the feature scope to a string. You should implement the FeatureScopeable contract.')
default => throw new RuntimeException('Unable to serialize the feature scope to a string. You should implement the FeatureScopeSerializeable contract.')
};
}

Expand Down
46 changes: 39 additions & 7 deletions tests/Feature/DatabaseDriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Illuminate\Support\Str;
use InvalidArgumentException;
use Laravel\Pennant\Contracts\FeatureScopeable;
use Laravel\Pennant\Contracts\FeatureScopeSerializeable;
use Laravel\Pennant\Events\AllFeaturesPurged;
use Laravel\Pennant\Events\DynamicallyRegisteringFeatureClass;
use Laravel\Pennant\Events\FeatureDeleted;
Expand Down Expand Up @@ -356,21 +357,27 @@ public function test_scope_can_be_strings_like_email_addresses()

public function test_it_can_handle_feature_scopeable_objects()
{
$scopeable = fn () => new class extends User implements FeatureScopeable
Feature::define('foo', function ($scope) {
return $scope === '[email protected]';
});
$scopeable = fn ($email) => new class(['email' => $email]) extends User implements FeatureScopeable
{
public function toFeatureIdentifier($driver): mixed
{
return '[email protected]';
return $this->email;
}
};

Feature::for($scopeable())->activate('foo');
$this->assertTrue(Feature::for($scopeable('[email protected]'))->active('foo'));
$this->assertFalse(Feature::for($scopeable('[email protected]'))->active('foo'));

$this->assertFalse(Feature::for('[email protected]')->active('foo'));
$this->assertTrue(Feature::for('[email protected]')->active('foo'));
$this->assertTrue(Feature::for($scopeable())->active('foo'));
$this->assertCount(4, DB::getQueryLog());

$this->assertCount(2, DB::getQueryLog());
$scope = DB::table('features')->get('scope');
$this->assertSame([
'[email protected]',
'[email protected]',
], $scope->pluck('scope')->all());
}

public function test_it_serializes_eloquent_models()
Expand All @@ -382,6 +389,31 @@ public function test_it_serializes_eloquent_models()
$this->assertStringContainsString('Workbench\App\Models\User|1', $scope);
}

public function test_it_can_manually_serialize_scope()
{
Feature::define('foo', function ($scope) {
return $scope->email === '[email protected]';
});
$scopeable = fn ($email) => new class(['email' => $email]) extends User implements FeatureScopeSerializeable
{
public function featureScopeSerialize(string $driver): string
{
return $this->email;
}
};

$this->assertTrue(Feature::for($scopeable('[email protected]'))->active('foo'));
$this->assertFalse(Feature::for($scopeable('[email protected]'))->active('foo'));

$this->assertCount(4, DB::getQueryLog());

$scope = DB::table('features')->get('scope');
$this->assertSame([
'[email protected]',
'[email protected]',
], $scope->pluck('scope')->all());
}

public function test_it_can_load_feature_state_into_memory()
{
$called = ['foo' => 0, 'bar' => 0];
Expand Down

0 comments on commit 4b3e6c1

Please sign in to comment.