Skip to content

Commit ddb54a7

Browse files
feat!: add shared state functionality, remove scoped context
1 parent a37edf8 commit ddb54a7

File tree

15 files changed

+364
-13
lines changed

15 files changed

+364
-13
lines changed

config/stateful-resources.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,17 @@
2828
|
2929
*/
3030
'default_state' => State::Full,
31+
32+
/*
33+
|--------------------------------------------------------------------------
34+
| Shared State
35+
|--------------------------------------------------------------------------
36+
|
37+
| Here you can specify whether the stateful resources should use a shared
38+
| for the resource state. If set to true, the state will be shared across
39+
| all resources. If set to false, the state will be scoped to the
40+
| resource instance.
41+
|
42+
*/
43+
'shared_state' => true,
3144
];

src/ActiveState.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace Farbcode\StatefulResources;
4+
5+
/**
6+
* ActiveState manages which state is currently active for resources.
7+
*/
8+
class ActiveState
9+
{
10+
private ?string $sharedState;
11+
12+
private array $resourceStates = [];
13+
14+
public function __construct()
15+
{
16+
$this->sharedState = null;
17+
}
18+
19+
/**
20+
* Set the shared state for all resources.
21+
*/
22+
public function setShared(string $state): void
23+
{
24+
$this->sharedState = $state;
25+
}
26+
27+
/**
28+
* Get the shared state.
29+
*/
30+
public function getShared(): string
31+
{
32+
return $this->sharedState ?? app(StateRegistry::class)->getDefaultState();
33+
}
34+
35+
/**
36+
* Set the state for a specific resource class.
37+
*/
38+
public function setForResource(string $resourceClass, string $state): void
39+
{
40+
$this->resourceStates[$resourceClass] = $state;
41+
}
42+
43+
/**
44+
* Get the state for a specific resource class.
45+
*/
46+
public function getForResource(string $resourceClass): string
47+
{
48+
return $this->resourceStates[$resourceClass] ?? app(StateRegistry::class)->getDefaultState();
49+
}
50+
}

src/Builder.php

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use Farbcode\StatefulResources\Concerns\ResolvesState;
66
use Farbcode\StatefulResources\Contracts\ResourceState;
7-
use Illuminate\Support\Facades\Context;
87

98
/**
109
* Builder for creating resource instances with a specific state.
@@ -31,26 +30,24 @@ public function __construct(string $resourceClass, string|ResourceState $state)
3130

3231
$this->resourceClass = $resourceClass;
3332
$this->state = $registeredState;
33+
34+
$this->setActiveState($this->resourceClass, $this->state);
3435
}
3536

3637
/**
3738
* Create a single resource instance.
3839
*/
3940
public function make($resource)
4041
{
41-
return Context::scope(function () use ($resource) {
42-
return $this->resourceClass::make($resource);
43-
}, ['resource-state-'.$this->resourceClass => $this->state]);
42+
return $this->resourceClass::make($resource);
4443
}
4544

4645
/**
4746
* Create a resource collection.
4847
*/
4948
public function collection($resource)
5049
{
51-
return Context::scope(function () use ($resource) {
52-
return $this->resourceClass::collection($resource);
53-
}, ['resource-state-'.$this->resourceClass => $this->state]);
50+
return $this->resourceClass::collection($resource);
5451
}
5552

5653
/**

src/Concerns/ResolvesState.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Farbcode\StatefulResources\Concerns;
44

5+
use Farbcode\StatefulResources\ActiveState;
56
use Farbcode\StatefulResources\Contracts\ResourceState;
67
use Farbcode\StatefulResources\StateRegistry;
78
use Illuminate\Support\Str;
@@ -46,4 +47,24 @@ protected static function resolveStateFromMethodName(string $state): ?string
4647

4748
return null;
4849
}
50+
51+
/**
52+
* Get the active state for the resource.
53+
*/
54+
protected function getActiveState($resourceClass): string
55+
{
56+
return config()->boolean('stateful-resources.shared_state', false)
57+
? app(ActiveState::class)->getShared()
58+
: app(ActiveState::class)->getForResource($resourceClass);
59+
}
60+
61+
/**
62+
* Set the active state for the resource.
63+
*/
64+
protected function setActiveState(string $resourceClass, string $state): void
65+
{
66+
config()->boolean('stateful-resources.shared_state', false)
67+
? app(ActiveState::class)->setShared($state)
68+
: app(ActiveState::class)->setForResource($resourceClass, $state);
69+
}
4970
}

src/StatefulJsonResource.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use Farbcode\StatefulResources\Concerns\StatefullyLoadsAttributes;
77
use Farbcode\StatefulResources\Contracts\ResourceState;
88
use Illuminate\Http\Resources\Json\JsonResource;
9-
use Illuminate\Support\Facades\Context;
109

1110
abstract class StatefulJsonResource extends JsonResource
1211
{
@@ -37,9 +36,7 @@ protected function getState(): string
3736
*/
3837
public function __construct($resource)
3938
{
40-
$defaultState = app(StateRegistry::class)->getDefaultState();
41-
42-
$this->state = Context::get('resource-state-'.static::class, $defaultState);
39+
$this->state = $this->getActiveState(static::class);
4340
parent::__construct($resource);
4441
}
4542

src/StatefulResourcesServiceProvider.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,9 @@ public function bootingPackage(): void
3939

4040
return $registry;
4141
});
42+
43+
$this->app->singleton(ActiveState::class, function () {
44+
return new ActiveState;
45+
});
4246
}
4347
}

tests/Feature/SharedStateTest.php

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
use Farbcode\StatefulResources\ActiveState;
4+
use Workbench\App\Http\Resources\CatResource;
5+
use Workbench\App\Models\Cat;
6+
use Workbench\App\Models\Dog;
7+
8+
it('can use shared state from previously called states in other resources', function () {
9+
$cat = Cat::factory()->new()->createOne();
10+
$dogs = Dog::factory()->count(3)->create();
11+
12+
$cat->enemies()->attach($dogs->pluck('id'));
13+
14+
$resource = CatResource::state('table')->make($cat)->toJson();
15+
16+
expect(app(ActiveState::class)->getShared())->toBe('table');
17+
18+
expect($resource)->toBeJson();
19+
20+
expect($resource)->json()->toEqual([
21+
'id' => $cat->id,
22+
'name' => $cat->name,
23+
'breed' => $cat->breed,
24+
'enemies' => [
25+
[
26+
'id' => $dogs[0]->id,
27+
'name' => $dogs[0]->name,
28+
],
29+
[
30+
'id' => $dogs[1]->id,
31+
'name' => $dogs[1]->name,
32+
],
33+
[
34+
'id' => $dogs[2]->id,
35+
'name' => $dogs[2]->name,
36+
],
37+
],
38+
]);
39+
});
40+
41+
it('works correctly when shared state is disabled', function () {
42+
config()->set('stateful-resources.shared_state', false);
43+
44+
$cat = Cat::factory()->new()->createOne();
45+
$dogs = Dog::factory()->count(3)->create();
46+
47+
$cat->enemies()->attach($dogs->pluck('id'));
48+
49+
$resource = CatResource::state('table')->make($cat)->toJson();
50+
51+
expect(app(ActiveState::class)->getForResource(CatResource::class))->toBe('table');
52+
53+
expect($resource)->toBeJson();
54+
55+
expect($resource)->json()->toEqual([
56+
'id' => $cat->id,
57+
'name' => $cat->name,
58+
'breed' => $cat->breed,
59+
'enemies' => [
60+
[
61+
'id' => $dogs[0]->id,
62+
'name' => $dogs[0]->name,
63+
'breed' => $dogs[0]->breed,
64+
],
65+
[
66+
'id' => $dogs[1]->id,
67+
'name' => $dogs[1]->name,
68+
'breed' => $dogs[1]->breed,
69+
],
70+
[
71+
'id' => $dogs[2]->id,
72+
'name' => $dogs[2]->name,
73+
'breed' => $dogs[2]->breed,
74+
],
75+
],
76+
]);
77+
});

workbench/app/Http/Resources/CatResource.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Farbcode\StatefulResources\Enums\State;
66
use Farbcode\StatefulResources\StatefulJsonResource;
77
use Illuminate\Http\Request;
8+
use Illuminate\Http\Resources\MissingValue;
89

910
class CatResource extends StatefulJsonResource
1011
{
@@ -19,11 +20,12 @@ public function toArray(Request $request): array
1920
'id' => $this->id,
2021
'name' => $this->name,
2122
'breed' => $this->whenStateIn([State::Full, State::Table], $this->breed),
22-
'fluffyness' => $this->whenStateIn([State::Full], $this->fluffyness),
23-
'color' => $this->whenStateIn([State::Full], $this->color),
23+
'fluffyness' => $this->whenStateFull($this->fluffyness),
24+
'color' => $this->whenState(State::Full, $this->color),
2425
'custom_field' => $this->whenState('custom', 'custom_value'),
2526
'snake_custom_field' => $this->whenStateSnakeCustom('snake_custom_value'),
2627
'kebab_custom_field' => $this->whenStateKebabCustom('kebab_custom_value'),
28+
'enemies' => $this->enemies->count() ? DogResource::collection($this->enemies) : new MissingValue,
2729
];
2830
}
2931
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Workbench\App\Http\Resources;
4+
5+
use Farbcode\StatefulResources\Enums\State;
6+
use Farbcode\StatefulResources\StatefulJsonResource;
7+
use Illuminate\Http\Request;
8+
9+
class DogResource extends StatefulJsonResource
10+
{
11+
/**
12+
* Transform the resource into an array.
13+
*
14+
* @return array<string, mixed>
15+
*/
16+
public function toArray(Request $request): array
17+
{
18+
return [
19+
'id' => $this->id,
20+
'name' => $this->name,
21+
'breed' => $this->whenState(State::Full, $this->breed),
22+
];
23+
}
24+
}

workbench/app/Models/Cat.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,9 @@ class Cat extends Model
2424
* @var array<string>
2525
*/
2626
protected $fillable = ['name', 'breed', 'fluffyness', 'color'];
27+
28+
public function enemies()
29+
{
30+
return $this->belongsToMany(Dog::class, 'cats_dogs');
31+
}
2732
}

0 commit comments

Comments
 (0)