Skip to content

Commit 63b9b27

Browse files
Merge pull request #3 from farbcodegmbh/feat/shared-state
[Feature]: Shared State
2 parents 916188b + dbd5e84 commit 63b9b27

19 files changed

+652
-17
lines changed

composer.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"keywords": [
55
"laravel",
66
"laravel-stateful-resources",
7-
"api",
7+
"api",
88
"resources"
99
],
1010
"homepage": "https://github.com/farbcodegmbh/laravel-stateful-resources",
@@ -48,7 +48,10 @@
4848
"Workbench\\App\\": "workbench/app/",
4949
"Workbench\\Database\\Factories\\": "workbench/database/factories/",
5050
"Workbench\\Database\\Seeders\\": "workbench/database/seeders/"
51-
}
51+
},
52+
"files": [
53+
"src/helpers.php"
54+
]
5255
},
5356
"scripts": {
5457
"post-autoload-dump": [

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
];

docs/.vitepress/config.mts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ export default defineConfig({
4343
{
4444
text: 'Advanced Usage',
4545
items: [
46-
{ text: 'Extending States', link: '/extending-states' }
46+
{ text: 'Extending States', link: '/extending-states' },
47+
{ text: 'State Sharing', link: '/state-sharing' },
4748
]
4849
}
4950
],

docs/pages/state-sharing.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# State Sharing
2+
3+
State sharing lets you set a resource state once and have it automatically applied to all subsequent resources using the same state—ideal for keeping nested or related resources in sync without repeating state declarations.
4+
5+
## Enabling Shared State
6+
7+
By default, state sharing is enabled. To disable it or change the default, update your `config/stateful-resources.php`:
8+
9+
```php
10+
'shared_state' => true,
11+
```
12+
13+
::: info
14+
Disable shared state when you prefer to assign states explicitly on each resource.
15+
:::
16+
17+
## Setting a Shared State
18+
19+
When you set a state on a resource, all further resources will inherit that state unless you override it:
20+
21+
```php
22+
UserResource::state(State::Minimal)->make($user);
23+
InvoiceResource::make($invoice); // Also in the Minimal state
24+
SubscriptionResource::state(State::Full)->make($subscription); // Overrides to Full
25+
```
26+
27+
## Nested Resources Example
28+
29+
In nested resources, the shared state will be used automatically:
30+
31+
```php
32+
class UserResource extends StatefulJsonResource
33+
{
34+
public function toArray(Request $request): array
35+
{
36+
return [
37+
'id' => $this->id,
38+
'name' => $this->name,
39+
'email' => $this->email,
40+
'alias' => $this->whenStateFull($this->alias),
41+
'invoices' => $this->whenStateFull(InvoiceResource::collection($this->invoices)), // InvoiceResource automatically has the same state as UserResource
42+
];
43+
}
44+
}
45+
```
46+
47+
## Facade & Helper
48+
You may also set the shared state explicitly through the `ActiveState` facade or the `activeResourceState` helper function instead:
49+
50+
```php
51+
use Farbcode\StatefulResources\Facades\ActiveState;
52+
53+
ActiveState::set('minimal');
54+
// or
55+
activeResourceState()->set('minimal');
56+
```

src/ActiveState.php

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
3+
namespace Farbcode\StatefulResources;
4+
5+
use Farbcode\StatefulResources\Concerns\ResolvesState;
6+
use Farbcode\StatefulResources\Contracts\ResourceState;
7+
8+
/**
9+
* ActiveState manages which state is currently active for resources.
10+
*/
11+
class ActiveState
12+
{
13+
use ResolvesState;
14+
15+
private ?string $sharedState;
16+
17+
private array $resourceStates = [];
18+
19+
public function __construct()
20+
{
21+
$this->sharedState = null;
22+
}
23+
24+
/**
25+
* Set the shared state for all resources.
26+
*/
27+
public function setShared(string|ResourceState $state): void
28+
{
29+
$state = $this->resolveState($state);
30+
$this->sharedState = $state;
31+
}
32+
33+
/**
34+
* Get the shared state.
35+
*/
36+
public function getShared(): string
37+
{
38+
return $this->sharedState ?? app(StateRegistry::class)->getDefaultState();
39+
}
40+
41+
/**
42+
* Check if a specific resource class has a state set.
43+
*/
44+
public function matchesShared(string|ResourceState $state): bool
45+
{
46+
$state = $this->resolveState($state);
47+
48+
return $this->getShared() === $state;
49+
}
50+
51+
/**
52+
* Set the state for a specific resource class.
53+
*/
54+
public function setForResource(string $resourceClass, string|ResourceState $state): void
55+
{
56+
$state = $this->resolveState($state);
57+
$this->resourceStates[$resourceClass] = $state;
58+
}
59+
60+
/**
61+
* Get the state for a specific resource class.
62+
*/
63+
public function getForResource(string $resourceClass): string
64+
{
65+
return $this->resourceStates[$resourceClass] ?? app(StateRegistry::class)->getDefaultState();
66+
}
67+
68+
/**
69+
* Check if the current state matches the resource's state.
70+
*/
71+
public function matchesResource(string $resourceClass, string|ResourceState $state): bool
72+
{
73+
$state = $this->resolveState($state);
74+
75+
return $this->getForResource($resourceClass) === $state;
76+
}
77+
78+
/**
79+
* Get the current state for a resource.
80+
* If no resource class is provided, returns the shared state.
81+
*/
82+
public function get(?string $resourceClass = null): string
83+
{
84+
if ($resourceClass === null) {
85+
return $this->getShared();
86+
}
87+
88+
return $this->getForResource($resourceClass);
89+
}
90+
91+
/**
92+
* Set the current state for a resource.
93+
* If no resource class is provided, sets the shared state.
94+
*/
95+
public function set(string|ResourceState $state, ?string $resourceClass = null): void
96+
{
97+
$state = $this->resolveState($state);
98+
99+
if ($resourceClass === null) {
100+
$this->setShared($state);
101+
} else {
102+
$this->setForResource($resourceClass, $state);
103+
}
104+
}
105+
106+
/**
107+
* Check if the current state matches the given state for a resource.
108+
* If no resource class is provided, checks against the shared state.
109+
*/
110+
public function matches(string|ResourceState $state, ?string $resourceClass = null): bool
111+
{
112+
$state = $this->resolveState($state);
113+
114+
if ($resourceClass === null) {
115+
return $this->matchesShared($state);
116+
}
117+
118+
return $this->matchesResource($resourceClass, $state);
119+
}
120+
}

src/Builder.php

Lines changed: 10 additions & 8 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.
@@ -13,6 +12,9 @@ class Builder
1312
{
1413
use ResolvesState;
1514

15+
/**
16+
* @var class-string<StatefulJsonResource>
17+
*/
1618
private string $resourceClass;
1719

1820
private string $state;
@@ -34,21 +36,21 @@ public function __construct(string $resourceClass, string|ResourceState $state)
3436
/**
3537
* Create a single resource instance.
3638
*/
37-
public function make($resource)
39+
public function make(...$parameters)
3840
{
39-
return Context::scope(function () use ($resource) {
40-
return $this->resourceClass::make($resource);
41-
}, ['resource-state-'.$this->resourceClass => $this->state]);
41+
$this->setActiveState($this->resourceClass, $this->state);
42+
43+
return $this->resourceClass::make(...$parameters);
4244
}
4345

4446
/**
4547
* Create a resource collection.
4648
*/
4749
public function collection($resource)
4850
{
49-
return Context::scope(function () use ($resource) {
50-
return $this->resourceClass::collection($resource);
51-
}, ['resource-state-'.$this->resourceClass => $this->state]);
51+
$this->setActiveState($this->resourceClass, $this->state);
52+
53+
return $this->resourceClass::collection($resource);
5254
}
5355

5456
/**

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/Facades/ActiveState.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Farbcode\StatefulResources\Facades;
4+
5+
use Farbcode\StatefulResources\ActiveState as ActiveStateService;
6+
use Farbcode\StatefulResources\Contracts\ResourceState;
7+
use Illuminate\Support\Facades\Facade;
8+
9+
/**
10+
* @method static void setShared(string|ResourceState $state)
11+
* @method static string getShared()
12+
* @method static void setForResource(string $resourceClass, string|ResourceState $state)
13+
* @method static string getForResource(string $resourceClass)
14+
* @method static bool matchesShared(string|ResourceState $state)
15+
* @method static bool matchesResource(string $resourceClass, string|ResourceState $state)
16+
* @method static string get(?string $resourceClass = null)
17+
* @method static void set(string|ResourceState $state, ?string $resourceClass = null)
18+
* @method static bool matches(string|ResourceState $state, ?string $resourceClass = null)
19+
*/
20+
class ActiveState extends Facade
21+
{
22+
/**
23+
* Get the registered name of the component.
24+
*/
25+
protected static function getFacadeAccessor(): string
26+
{
27+
return ActiveStateService::class;
28+
}
29+
}

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
}

0 commit comments

Comments
 (0)