Skip to content

Commit 2352109

Browse files
committed
feat:
- allowed classes for object casts - Api Enum Support - Builder restructuring changes: -create now internally calls updateOrCreate
1 parent 49768c3 commit 2352109

20 files changed

+728
-130
lines changed

README.md

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ This Laravel package aims to store and manage user settings/preferences in a sim
2323
* [Custom Caster](#custom-caster)
2424
* [Custom Rules](#custom-rules)
2525
* [Policies](#policies)
26+
* [Preference Building](#preference-building)
2627
* [Routing](#routing)
2728
* [Anantomy](#anantomy)
2829
* [Example](#example)
@@ -48,7 +49,8 @@ This Laravel package aims to store and manage user settings/preferences in a sim
4849
### Roadmap
4950

5051
- Additional inbuilt Custom Rules -> v2.x
51-
- Api Enum support -> v2.1
52+
- Allow array of preferenceBuilders in initBuk -> v2.1.1
53+
- Event System -> v2.2
5254
- Suggestions are welcome
5355

5456
## Installation
@@ -58,8 +60,9 @@ You can install the package via composer:
5860
```bash
5961
composer require matteoc99/laravel-preference
6062
```
61-
consider installing also `graham-campbell/security-core:^4.0` to take advantage of xss cleaning. see [Security](#security) for more information
6263

64+
consider installing also `graham-campbell/security-core:^4.0` to take advantage of xss cleaning.
65+
see [Security](#security) for more information
6366

6467
You can publish the config file with:
6568

@@ -126,10 +129,11 @@ enum Preferences :string implements PreferenceGroup
126129
case QUALITY="quality";
127130
case CONFIG="configuration";
128131
}
132+
129133
enum General :string implements PreferenceGroup
130134
{
131135
case LANGUAGE="language";
132-
case THEME="quality";
136+
case THEME="theme";
133137
}
134138
```
135139

@@ -194,7 +198,7 @@ return new class extends Migration {
194198
{
195199

196200
PreferenceBuilder::initBulk($this->preferences(),
197-
true/false // nullable for the whole Bulk
201+
true // nullable for the whole Bulk
198202
);
199203
}
200204

@@ -267,7 +271,7 @@ class User extends \Illuminate\Foundation\Auth\User implements PreferenceableMod
267271

268272
```php
269273
$user->setPreference(Preferences::LANGUAGE,"de");
270-
$user->getPreference(Preferences::LANGUAGE,); // 'de' as string
274+
$user->getPreference(Preferences::LANGUAGE); // 'de' as string
271275

272276
$user->setPreference(Preferences::LANGUAGE,"fr");
273277
// ValidationException because of the rule: ->withRule(new InRule("en","it","de"))
@@ -412,12 +416,24 @@ implement `PreferencePolicy` and the 4 methods defined by the contract
412416

413417
````
414418

419+
## Preference Building
420+
421+
| Single-Mode | Bulk-Mode (array-keys) | Constrains | Description |
422+
|-------------------------------------|---------------------------------------------|---------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
423+
| init(>name<,>cast<) | ```["name"=> >name<]``` | \>name< = instanceof PreferenceGroup | Unique identifier for the preference |
424+
| init(>name<,>cast<) | ```["cast"=> >cast<]``` | \>cast< = instanceof CastableEnum | Caster to translate the value between all different scenarios. Currently: Api-calls as well as saving to and retrieving fron the DB |
425+
| nullable(>nullable<) | ```["nullable"=> >nullable<]``` | \>nullable< = bool | Whether the default value can be null and if the preference can be set to null |
426+
| withDefaultValue(>default_value<) | ```["default_value"=> >default_value<]``` | \>default_value< = mixed, but must comply with the cast & validationRule | Initial value for this preference |
427+
| withDescription(>description<) | ```["description"=> >description<]``` | \>description< = string | Legacy code from v1.x has no actual use as of now |
428+
| withPolicy(>policy<) | ```["policy"=> >policy<]``` | \>policy< = instanceof PreferencePolicy | Authorize actions such as update/delete etc. on certain preferences. |
429+
| withRule(>rule<) | ```["rule"=> >rule<]``` | \>rule< = instanceof ValidationRule | Additional validation Rule, to validate values before setting them |
430+
| setAllowedClasses(>allowed_values<) | ```["allowed_values"=> >allowed_values<]``` | \>allowed_values< = array of string classes. For non Primitive Casts only | Current use-cases: <br/> - restrict classes of enum or object that can be set to this preference<br/> - reconstruct the original class when sending data via api. |
431+
415432
## Routing
416433

417434
off by default, enable it in the config
418435

419-
> Current limitation: it's not possible to set enums/object casts via API
420-
> Enum support planned for v2.2
436+
> Current limitation: it's not possible to set object casts via API
421437
422438
### Anantomy:
423439

@@ -480,11 +496,19 @@ will result in:
480496
(my_preferences.user.general.index)
481497
equivalent to: `$user->getPreferences(General::class)`
482498

499+
```shell
500+
curl -X GET 'https://example.com/my_preferences/user/{scope_id}/general'
501+
```
502+
483503
#### GET
484504

485505
(my_preferences.user.general.get)
486506
equivalent to: `$user->getPreference(General::{preference})`
487507

508+
```shell
509+
curl -X GET 'https://example.com/my_preferences/user/{scope_id}/general/{preference}'
510+
```
511+
488512
#### UPDATE
489513

490514
(my_preferences.user.general.update)
@@ -494,35 +518,69 @@ Payload:
494518
'value' => >value<
495519
}
496520

521+
```shell
522+
curl -X PATCH 'https://example.com/my_preferences/user/{scope_id}/general/{preference}' \
523+
-d '{"value": "new_value"}'
524+
```
525+
526+
##### Enum Patching
527+
528+
When creating your enum preference, add `setAllowedClasses` containing the possible enums to reconstruct the value
529+
> ! if multiple cases are shared between enums, the first match is taken !
530+
> -> consider having only one enum per preference to avoid overlaps
531+
532+
then, when sending the value it varies:
533+
534+
- BackedEnum: send the value or the case
535+
- UnitBacked: send the case
536+
537+
Example:
538+
```php
539+
enum Theme
540+
{
541+
case LIGHT;
542+
case DARK;
543+
}
544+
curl -X PATCH 'https://example.com/my_preferences/user/{scope_id}/general/{preference}' \
545+
-d '{"value": "DARK"}'
546+
```
547+
497548
#### DELETE
498549

499550
(my_preferences.user.general.delete)
500551
equivalent to: `$user->removePreference(General::{preference})`
501552

553+
```shell
554+
curl -X DELETE 'https://example.com/my_preferences/user/{scope_id}/general/{preference}'
555+
```
556+
502557
### Middlewares
503558

504559
set global or context specific middlewares
505560
in the config file
506561

507562
```php
508563
'middlewares' => [
564+
'web', // required for Auth::user() and policies
509565
'auth', //no key => general middleware which gets applied to all routes
510566
'user'=> 'verified', // scoped middleware only for user routes should you have other preferencable models
511567
'user.general'=> 'verified' // scoped & grouped middleware only for a specific model + enum
512568
],
513569
```
514570

571+
**known Issues**: without the web middleware, you won't have access to the user via the Auth facade
572+
since it's set by the middleware. Looking into an alternative
573+
515574
## Security
516575

517-
XSS cleaning is only performed on user facing api calls.
576+
XSS cleaning is only performed on user facing api calls.
518577
this can be disabled, if not required, with the config: `user_preference.xss_cleaning`
519578

520-
When setting preferences directly via `setPreference`
579+
When setting preferences directly via `setPreference`
521580
this cleaning step is assumed to have already been performed, if necessary.
522581

523582
Consider installing [Security-Core](https://github.com/GrahamCampbell/Security-Core) to make use of this feature
524583

525-
526584
## Upgrade from v1
527585

528586
- implement `PreferenceGroup` in your Preference enums

src/Contracts/CastableEnum.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ public function castFromString(string $value): mixed;
1818

1919
/**
2020
* used by the Controller to cast to json
21+
*
2122
* @param mixed $value
2223
*
2324
* @return array
2425
*/
2526
public function castToDto(mixed $value): array;
27+
2628
}

src/Enums/Cast.php

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public function validation(): ValidationRule|array|string|null
4040
self::STRING => 'string',
4141
self::BOOL => 'boolean',
4242
self::ARRAY => 'array',
43-
self::DATE => new OrRule('date','date_format:Y-m-d', new InstanceOfRule(Carbon::class)),
43+
self::DATE => new OrRule('date', 'date_format:Y-m-d', new InstanceOfRule(Carbon::class)),
4444
self::DATETIME => new OrRule('date', new InstanceOfRule(Carbon::class)),
4545
self::TIME => new OrRule('date_format:H:i', 'date_format:H:i:s', new InstanceOfRule(Carbon::class)),
4646
self::TIMESTAMP => new OrRule('date_format:U', new InstanceOfRule(Carbon::class)),
@@ -126,6 +126,15 @@ private function ensureType(mixed $value): mixed
126126
};
127127
}
128128

129+
public function isPrimitive(): bool
130+
{
131+
return match ($this) {
132+
self::BACKED_ENUM,
133+
self::ENUM,
134+
self::OBJECT => false,
135+
default => true,
136+
};
137+
}
129138

130139
private function ensureArray(mixed $value): array
131140
{
@@ -187,7 +196,15 @@ private function valueToArray(mixed $value): array
187196
'value' => $value->toArray()
188197
];
189198
}
190-
199+
if (is_object($value) && in_array(\BackedEnum::class, class_implements($value))) {
200+
return [
201+
'value' => $value->value
202+
];
203+
} else if (is_object($value) && in_array(\UnitEnum::class, class_implements($value))) {
204+
return [
205+
'value' => $value->name
206+
];
207+
}
191208
if (!is_array($value)) {
192209
return ['value' => is_string($value) ? $value : $this->castToString($value)];
193210
}
@@ -196,5 +213,4 @@ private function valueToArray(mixed $value): array
196213
'value' => $value
197214
];
198215
}
199-
200216
}

0 commit comments

Comments
 (0)