Skip to content

Commit a342d32

Browse files
julian-farbcodeRaphaelStoerkace-of-aces
committed
feat: initial implementation
Co-authored-by: Raphael Störk <raphael.stoerk@farbcode.net> Co-authored-by: Julian Schramm <hi@julian.center>
1 parent 7ad2a81 commit a342d32

File tree

4 files changed

+284
-1
lines changed

4 files changed

+284
-1
lines changed

src/Builder.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Farbcode\StatefulResources;
4+
5+
use Farbcode\StatefulResources\Enums\ResourceState;
6+
use Illuminate\Support\Facades\Context;
7+
8+
class Builder
9+
{
10+
private string $resourceClass;
11+
12+
private ResourceState $state;
13+
14+
public function __construct(string $resourceClass, ResourceState $state)
15+
{
16+
$this->resourceClass = $resourceClass;
17+
$this->state = $state;
18+
}
19+
20+
/**
21+
* Create a single resource instance.
22+
*/
23+
public function make($resource)
24+
{
25+
return Context::scope(function () use ($resource) {
26+
return $this->resourceClass::make($resource);
27+
}, ['resource-state-'.$this->resourceClass => $this->state]);
28+
}
29+
30+
/**
31+
* Create a resource collection.
32+
*/
33+
public function collection($resource)
34+
{
35+
return Context::scope(function () use ($resource) {
36+
return $this->resourceClass::collection($resource);
37+
}, ['resource-state-'.$this->resourceClass => $this->state]);
38+
}
39+
40+
/**
41+
* Magic method to handle direct instantiation.
42+
*/
43+
public function __invoke($resource)
44+
{
45+
return $this->make($resource);
46+
}
47+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
<?php
2+
3+
namespace Farbcode\StatefulResources\Concerns;
4+
5+
use Farbcode\StatefulResources\Enums\ResourceState;
6+
use Illuminate\Http\Resources\ConditionallyLoadsAttributes;
7+
use Illuminate\Http\Resources\MergeValue;
8+
use Illuminate\Http\Resources\MissingValue;
9+
10+
/**
11+
* @see \Illuminate\Http\Resources\ConditionallyLoadsAttributes
12+
*
13+
* @method MissingValue|mixed whenStateMinimal(mixed $value, mixed $default = null)
14+
* @method MissingValue|mixed unlessStateMinimal(mixed $value, mixed $default = null)
15+
* @method MissingValue|mixed whenStateFull(mixed $value, mixed $default = null)
16+
* @method MissingValue|mixed unlessStateFull(mixed $value, mixed $default = null)
17+
* @method MissingValue|mixed whenStateTable(mixed $value, mixed $default = null)
18+
* @method MissingValue|mixed unlessStateTable(mixed $value, mixed $default = null)
19+
* @method MergeValue|mixed mergeWhenStateMinimal(mixed $value, mixed $default = null)
20+
* @method MergeValue|mixed mergeUnlessStateMinimal(mixed $value, mixed $default = null)
21+
* @method MergeValue|mixed mergeWhenStateFull(mixed $value, mixed $default = null)
22+
* @method MergeValue|mixed mergeUnlessStateFull(mixed $value, mixed $default = null)
23+
* @method MissingValue|mixed whenStateTable(mixed $value, mixed $default = null)
24+
* @method MissingValue|mixed unlessStateTable(mixed $value, mixed $default = null)
25+
*/
26+
trait StatefullyLoadsAttributes
27+
{
28+
use ConditionallyLoadsAttributes;
29+
30+
/**
31+
* Retrieve a value if the current state matches the given state.
32+
*
33+
* @param ResourceState $state
34+
* @param mixed $value
35+
* @param mixed $default
36+
* @return \Illuminate\Http\Resources\MissingValue|mixed
37+
*/
38+
protected function whenState($state, $value, $default = null)
39+
{
40+
if (func_num_args() === 3) {
41+
return $this->when($this->getState() === $state, $value, $default);
42+
}
43+
44+
return $this->when($this->getState() === $state, $value);
45+
}
46+
47+
/**
48+
* Retrieve a value unless the current state matches the given state.
49+
*
50+
* @param ResourceState $state
51+
* @param mixed $value
52+
* @param mixed $default
53+
* @return \Illuminate\Http\Resources\MissingValue|mixed
54+
*/
55+
protected function unlessState($state, $value, $default = null)
56+
{
57+
if (func_num_args() === 3) {
58+
return $this->unless($this->getState() === $state, $value, $default);
59+
}
60+
61+
return $this->unless($this->getState() === $state, $value);
62+
}
63+
64+
/**
65+
* Retrieve a value if the current state is one of the given states.
66+
*
67+
* @param array<ResourceState> $states
68+
* @param mixed $value
69+
* @param mixed $default
70+
* @return \Illuminate\Http\Resources\MissingValue|mixed
71+
*/
72+
protected function whenStateIn(array $states, $value, $default = null)
73+
{
74+
$condition = in_array($this->getState(), $states, true);
75+
76+
if (func_num_args() === 3) {
77+
return $this->when($condition, $value, $default);
78+
}
79+
80+
return $this->when($condition, $value);
81+
}
82+
83+
/**
84+
* Retrieve a value unless the current state is one of the given states.
85+
*
86+
* @param array<ResourceState> $states
87+
* @param mixed $value
88+
* @param mixed $default
89+
* @return \Illuminate\Http\Resources\MissingValue|mixed
90+
*/
91+
protected function unlessStateIn(array $states, $value, $default = null)
92+
{
93+
$condition = in_array($this->getState(), $states, true);
94+
95+
if (func_num_args() === 3) {
96+
return $this->unless($condition, $value, $default);
97+
}
98+
99+
return $this->unless($condition, $value);
100+
}
101+
102+
/**
103+
* Merge a value if the current state matches the given state.
104+
*
105+
* @param ResourceState $state
106+
* @param mixed $value
107+
* @param mixed $default
108+
* @return \Illuminate\Http\Resources\MergeValue|mixed
109+
*/
110+
protected function mergeWhenState($state, $value, $default = null)
111+
{
112+
if (func_num_args() === 3) {
113+
return $this->mergeWhen($this->getState() === $state, $value, $default);
114+
}
115+
116+
return $this->mergeWhen($this->getState() === $state, $value);
117+
}
118+
119+
/**
120+
* Merge a value unless the current state matches the given state.
121+
*
122+
* @param ResourceState $state
123+
* @param mixed $value
124+
* @param mixed $default
125+
* @return \Illuminate\Http\Resources\MergeValue|mixed
126+
*/
127+
protected function mergeUnlessState($state, $value, $default = null)
128+
{
129+
if (func_num_args() === 3) {
130+
return $this->mergeUnless($this->getState() === $state, $value, $default);
131+
}
132+
133+
return $this->mergeUnless($this->getState() === $state, $value);
134+
}
135+
136+
/**
137+
* Get the current state of the resource.
138+
* This method should be implemented by the class using this trait.
139+
*/
140+
abstract protected function getState(): ResourceState;
141+
142+
public function __call($method, $parameters)
143+
{
144+
$singleStateMethods = [
145+
'whenState',
146+
'unlessState',
147+
'mergeWhenState',
148+
'mergeUnlessState',
149+
];
150+
151+
foreach ($singleStateMethods as $singleStateMethod) {
152+
if (str_starts_with($method, $singleStateMethod)) {
153+
$state = strtolower(substr($method, strlen($singleStateMethod)));
154+
if (empty($state)) {
155+
continue;
156+
}
157+
158+
return $this->{$singleStateMethod}(ResourceState::from($state), ...$parameters);
159+
}
160+
}
161+
162+
return parent::__call($method, $parameters);
163+
}
164+
}

src/Enums/ResourceState.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Farbcode\StatefulResources\Enums;
4+
5+
enum ResourceState: string
6+
{
7+
case Minimal = 'minimal';
8+
case Table = 'table';
9+
case Full = 'full';
10+
}

src/StatefulJsonResource.php

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,66 @@
22

33
namespace Farbcode\StatefulResources;
44

5-
class StatefulJsonResource {}
5+
use Farbcode\StatefulResources\Concerns\StatefullyLoadsAttributes;
6+
use Farbcode\StatefulResources\Enums\ResourceState;
7+
use Illuminate\Http\Resources\Json\JsonResource;
8+
use Illuminate\Support\Facades\Context;
9+
10+
/**
11+
* @method static \Farbcode\StatefulResources\Builder minimal()
12+
* @method static \Farbcode\StatefulResources\Builder table()
13+
* @method static \Farbcode\StatefulResources\Builder full()
14+
*/
15+
abstract class StatefulJsonResource extends JsonResource
16+
{
17+
use StatefullyLoadsAttributes;
18+
19+
private ResourceState $state;
20+
21+
/**
22+
* Create a new stateful resource builder with a specific state.
23+
*/
24+
public static function state(ResourceState $state): Builder
25+
{
26+
return new Builder(static::class, $state);
27+
}
28+
29+
/**
30+
* Retrieve the state of the stateful resource.
31+
*/
32+
protected function getState(): ResourceState
33+
{
34+
return $this->state;
35+
}
36+
37+
/**
38+
* Create a new stateful resource instance.
39+
*
40+
* @param mixed $resource
41+
*/
42+
public function __construct($resource)
43+
{
44+
$this->state = Context::get('resource-state-'.static::class, ResourceState::Full);
45+
parent::__construct($resource);
46+
}
47+
48+
/**
49+
* Handle dynamic method calls for resource states.
50+
*
51+
* @param string $method
52+
* @param array $parameters
53+
* @return \Farbcode\StatefulResources\Builder
54+
*
55+
* @throws \BadMethodCallException
56+
*/
57+
public static function __callStatic($method, $parameters)
58+
{
59+
$state = ResourceState::tryFrom($method);
60+
61+
if ($state === null) {
62+
return parent::__callStatic($method, $parameters);
63+
}
64+
65+
return new Builder(static::class, $state);
66+
}
67+
}

0 commit comments

Comments
 (0)