Skip to content

Commit e2b8edd

Browse files
julian-farbcodeRaphaelStoerkace-of-aces
authored
Merge pull request #1 from farbcodegmbh/feat/custom-states
[Feature] Custom Resource States Co-authored-by: Raphael Störk <[email protected]> Co-authored-by: Julian Schramm <[email protected]>
2 parents 26412f6 + 68c362b commit e2b8edd

26 files changed

+872
-51
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
],
2222
"require": {
2323
"php": "^8.4",
24-
"illuminate/contracts": "^12.0",
24+
"illuminate/contracts": "^12.1",
2525
"spatie/laravel-package-tools": "^1.16"
2626
},
2727
"require-dev": {

config/stateful-resources.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,31 @@
11
<?php
22

3+
use Farbcode\StatefulResources\Enums\State;
4+
35
return [
4-
//
6+
/*
7+
|--------------------------------------------------------------------------
8+
| States
9+
|--------------------------------------------------------------------------
10+
|
11+
| Below you may register the resource states that you want to use inside
12+
| your stateful resources. These can be instances of a ResourceState
13+
| or simple strings.
14+
|
15+
*/
16+
'states' => [
17+
...State::cases(),
18+
//
19+
],
20+
21+
/*
22+
|--------------------------------------------------------------------------
23+
| Default State
24+
|--------------------------------------------------------------------------
25+
|
26+
| This state will be used when no state is explicitly set on the resource.
27+
| If not set, the first state in the states array will be used.
28+
|
29+
*/
30+
'default_state' => State::Full,
531
];

docs/.vitepress/config.mts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,15 @@ export default defineConfig({
3737
text: 'Basics',
3838
items: [
3939
{ text: 'Installation', link: '/installation' },
40+
{ text: 'Basic Usage', link: '/basic-usage' },
4041
]
4142
},
43+
{
44+
text: 'Advanced Usage',
45+
items: [
46+
{ text: 'Extending States', link: '/extending-states' }
47+
]
48+
}
4249
],
4350

4451
socialLinks: [

docs/pages/basic-usage.md

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# Basic Usage
2+
3+
Laravel Stateful Resources allows you to create dynamic API responses by changing the structure of your JSON resources based on different states. This is especially useful when you need to return different levels of detail for the same model depending on the context.
4+
5+
## Generating a Stateful Resource
6+
7+
The package provides an Artisan command to quickly generate a new stateful resource:
8+
9+
```bash
10+
php artisan make:stateful-resource UserResource
11+
```
12+
13+
This command creates a new resource class in `app/Http/Resources/` that extends `StatefulJsonResource`:
14+
15+
```php
16+
<?php
17+
18+
namespace App\Http\Resources;
19+
20+
use Illuminate\Http\Request;
21+
use Farbcode\StatefulResources\StatefulJsonResource;
22+
23+
class UserResource extends StatefulJsonResource
24+
{
25+
/**
26+
* Transform the resource into an array.
27+
*
28+
* @return array<string, mixed>
29+
*/
30+
public function toArray(Request $request): array
31+
{
32+
return parent::toArray($request);
33+
}
34+
}
35+
```
36+
37+
## Built-in States
38+
39+
The package comes with three built-in states defined in the `State` enum:
40+
41+
- **`Full`** - For all available attributes
42+
- **`Table`** - For attributes suitable for table/listing views
43+
- **`Minimal`** - For only essential attributes
44+
45+
See the [Extending States](extending-states.md) documentation for how to configure this and add custom states.
46+
47+
## Using States in Resources
48+
49+
Inside your stateful resource, you can use conditional methods to control which attributes are included based on the current state:
50+
51+
```php
52+
<?php
53+
54+
namespace App\Http\Resources;
55+
56+
use Farbcode\StatefulResources\Enums\State;
57+
use Farbcode\StatefulResources\StatefulJsonResource;
58+
use Illuminate\Http\Request;
59+
60+
class UserResource extends StatefulJsonResource
61+
{
62+
public function toArray(Request $request): array
63+
{
64+
return [
65+
'id' => $this->id,
66+
'name' => $this->name,
67+
'email' => $this->whenState(State::Full, $this->email),
68+
'profile' => $this->whenStateIn([State::Full], [
69+
'bio' => $this->bio,
70+
'avatar' => $this->avatar,
71+
'created_at' => $this->created_at,
72+
]),
73+
'role' => $this->whenStateIn([State::Full, State::Table], $this->role),
74+
'last_login' => $this->unlessState(State::Minimal, $this->last_login_at),
75+
];
76+
}
77+
}
78+
```
79+
80+
You can also use the string representation of states instead of enum cases:
81+
82+
```php
83+
'email' => $this->whenState('full', $this->email),
84+
'name' => $this->unlessState('minimal', $this->full_name),
85+
```
86+
87+
## Available Conditional Methods
88+
89+
The package provides several methods to conditionally include attributes:
90+
91+
### `whenState`
92+
93+
Include a value only when the current state matches the specified state:
94+
95+
```php
96+
'email' => $this->whenState(State::Full, $this->email),
97+
'admin_notes' => $this->whenState(State::Full, $this->admin_notes, 'N/A'),
98+
```
99+
100+
### `unlessState`
101+
102+
Include a value unless the current state matches the specified state:
103+
104+
```php
105+
'public_info' => $this->unlessState(State::Minimal, $this->public_information),
106+
```
107+
108+
### `whenStateIn`
109+
110+
Include a value when the current state is one of the specified states:
111+
112+
```php
113+
'detailed_info' => $this->whenStateIn([State::Full, State::Table], [
114+
'department' => $this->department,
115+
'position' => $this->position,
116+
]),
117+
```
118+
119+
### `unlessStateIn`
120+
121+
Include a value unless the current state is one of the specified states:
122+
123+
```php
124+
'sensitive_data' => $this->unlessStateIn([State::Minimal, State::Table], $this->sensitive_info),
125+
```
126+
127+
### Magic Conditionals
128+
129+
You can also use magic methods with for cleaner syntax:
130+
131+
```php
132+
'email' => $this->whenStateFull($this->email),
133+
'name' => $this->unlessStateMinimal($this->full_name),
134+
```
135+
136+
## Using Stateful Resources
137+
138+
### Setting the State Explicitly
139+
140+
Use the static `state()` method to create a resource with a specific state:
141+
142+
```php
143+
$user = UserResource::state(State::Minimal)->make($user);
144+
```
145+
146+
### Using Magic Methods
147+
148+
You can also use magic methods for a more fluent syntax:
149+
150+
```php
151+
// This is equivalent to the explicit state(State::Minimal) call
152+
$user = UserResource::minimal()->make($user);
153+
```
154+
155+
### Default State
156+
157+
If no state is specified, the resource will use the default state. You can change the default state in the package's configuration file: `config/stateful-resources.php`.
158+
159+
```php
160+
// Uses the default state
161+
$user = UserResource::make($user);
162+
```

docs/pages/extending-states.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Extending States
2+
3+
You may find yourself being too limited with the three State states included in the package's `State` enum.
4+
This package allows you to register custom states that you can then use in your resources.
5+
6+
## Registering Custom States
7+
8+
Before using a custom state, register it in the package's `stateful-resources.states` configuration:
9+
10+
```php
11+
<?php
12+
use Farbcode\StatefulResources\Enums\State;
13+
14+
return [
15+
'states' => [
16+
...State::cases(), // The built-in states
17+
'custom', // Your custom state as a string
18+
...CustomResourceState::cases(), // Or as cases of a custom enum
19+
],
20+
];
21+
```
22+
23+
## Creating a Custom State Enum
24+
25+
Instead of using strings, you may want to create your own state enum to define custom states. This enum should implement the `ResourceState` interface provided by the package.
26+
27+
```php
28+
<?php
29+
30+
namespace App\Enums;
31+
32+
use Farbcode\StatefulResources\Contracts\ResourceState;
33+
34+
enum CustomResourceState: string implements ResourceState
35+
{
36+
case Compact = 'compact';
37+
case Extended = 'extended';
38+
case Debug = 'debug';
39+
}
40+
```
41+
42+
## Using Custom States
43+
44+
Now that you have created and registered your custom state enum, you can use it just like the built-in states inside your resources.
45+
46+
```php
47+
<?php
48+
49+
namespace App\Http\Resources;
50+
51+
use Farbcode\StatefulResources\StatefulJsonResource;
52+
use App\Enums\CustomResourceState;
53+
54+
class UserResource extends StatefulJsonResource
55+
{
56+
public function toArray($request)
57+
{
58+
return [
59+
'id' => $this->id,
60+
'name' => $this->name,
61+
'email' => $this->whenState(CustomResourceState::Extended, $this->email),
62+
'debug_info' => $this->whenStateDebug([
63+
'created_at' => $this->created_at,
64+
'updated_at' => $this->updated_at,
65+
]),
66+
'avatar' => $this->unlessState('custom', $this->avatar),
67+
];
68+
}
69+
}
70+
```
71+
72+
You can then apply the custom states to your resource in the same way you would with the built-in states:
73+
74+
```php
75+
// Using the static method
76+
UserResource::state(CustomResourceState::Compact)->make($user);
77+
78+
// Using the magic method (if the state name matches the case name)
79+
UserResource::compact()->make($user);
80+
```

docs/pages/installation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
## Requirements
44

55
- PHP \>= 8.4
6-
- Laravel 12.x
6+
- Laravel \>= 12.1
77

88
## Installation
99

src/Builder.php

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,35 @@
22

33
namespace Farbcode\StatefulResources;
44

5-
use Farbcode\StatefulResources\Enums\ResourceState;
5+
use Farbcode\StatefulResources\Concerns\ResolvesState;
6+
use Farbcode\StatefulResources\Contracts\ResourceState;
67
use Illuminate\Support\Facades\Context;
78

9+
/**
10+
* Builder for creating resource instances with a specific state.
11+
*
12+
* @internal
13+
*/
814
class Builder
915
{
16+
use ResolvesState;
17+
1018
private string $resourceClass;
1119

12-
private ResourceState $state;
20+
private string $state;
1321

14-
public function __construct(string $resourceClass, ResourceState $state)
22+
public function __construct(string $resourceClass, string|ResourceState $state)
1523
{
24+
$state = $this->resolveState($state);
25+
26+
$registeredState = app(StateRegistry::class)->tryFrom($state);
27+
28+
if ($registeredState === null) {
29+
throw new \InvalidArgumentException("State \"{$state}\" is not registered.");
30+
}
31+
1632
$this->resourceClass = $resourceClass;
17-
$this->state = $state;
33+
$this->state = $registeredState;
1834
}
1935

2036
/**

src/Concerns/ResolvesState.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Farbcode\StatefulResources\Concerns;
4+
5+
use Farbcode\StatefulResources\Contracts\ResourceState;
6+
use Farbcode\StatefulResources\StateRegistry;
7+
8+
trait ResolvesState
9+
{
10+
/**
11+
* Resolve the value of a given state.
12+
*
13+
* @throws \InvalidArgumentException
14+
*/
15+
private function resolveState(ResourceState|string $state): string
16+
{
17+
$stateString = $state instanceof ResourceState ? (string) $state->value : $state;
18+
19+
if (app(StateRegistry::class)->tryFrom($stateString) === null) {
20+
throw new \InvalidArgumentException("State \"{$stateString}\" is not registered.");
21+
}
22+
23+
return $stateString;
24+
}
25+
}

0 commit comments

Comments
 (0)