Skip to content

Commit 46f7385

Browse files
committed
feat:
- readme updates - new Cast 'NONE' - artisan commands scaffolding - better and more streamlined validation fix: - timestamp conversion bug - updateOrCreate preference
1 parent 16f1006 commit 46f7385

16 files changed

+297
-78
lines changed

README.md

+23-3
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,25 @@ class User extends \Illuminate\Foundation\Auth\User implements PreferenceableMod
278278
279279
### Available Casts
280280

281-
INT, FLOAT, STRING, BOOL,
282-
ARRAY, TIME, DATE, DATETIME,
283-
TIMESTAMP, BACKED_ENUM, ENUM, OBJECT
281+
| Cast | Explanation |
282+
|-------------|-----------------------------------------------------------------------------------|
283+
| INT | Converts and Validates a value to be an integer. |
284+
| FLOAT | Converts and Validates a value to be a floating-point number. |
285+
| STRING | Converts and Validates a value to be a string. |
286+
| BOOL | Converts and Validates a value to be a boolean (regards non-empty as `true`). |
287+
| ARRAY | Converts and Validates a value to be an array. |
288+
| BACKED_ENUM | Ensures the value is a BackedEnum type. Useful for enums with underlying values. |
289+
| ENUM | Ensures the value is a UnitEnum type. Useful for enums without underlying values. |
290+
| OBJECT | Ensures that the value is an object. |
291+
| NONE | No casting is performed. Returns the value as-is. |
292+
293+
| Date-Casts | Explanation |
294+
|------------|--------------------------------------------------------------------------------------------------------------|
295+
| | Converts a value using Carbon::parse, and always return a Carbon instance. <br/> Validation is Cast-Specific |
296+
| DATE | sets the time to be `00:00`. |
297+
| TIME | Always uses the current date, setting only the time |
298+
| DATETIME | with both date and time(optionally). |
299+
| TIMESTAMP | allows a string/int timestamp or a carbon instance |
284300

285301
### Custom Caster
286302

@@ -458,6 +474,10 @@ in the config file
458474
- Signature changes on the trait: group got removed and name now requires a `PreferenceGroup`
459475
- Builder: setting group got removed and name now expects a `PreferenceGroup` enum
460476
- `DataRule` has been removed, add a constructor to get you own, tailored, params
477+
- database serialization incompatibilities will require you to rerun your Preference migrations
478+
- [single mode](#single-mode): make sure to use `updateOrCreate`,
479+
e.g ` PreferenceBuilder::init(VideoPreferences::QUALITY)->updateOrCreate();`
480+
- [bulk mode](#bulk-mode): initBulk as usual, as it works with upsert
461481

462482
## Test
463483

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
use Matteoc99\LaravelPreference\Models\Preference;
7+
8+
return new class extends Migration {
9+
10+
public function up()
11+
{
12+
13+
$preferenceTable = (new Preference())->getTable();
14+
15+
Schema::table($preferenceTable, function (Blueprint $table) {
16+
$table->renameColumn('guard', 'policy');
17+
});
18+
}
19+
20+
public function down()
21+
{
22+
$preferenceTable = (new Preference())->getTable();
23+
24+
Schema::table($preferenceTable, function (Blueprint $table) {
25+
$table->renameColumn('policy', 'guard');
26+
});
27+
}
28+
};

src/Casts/SerializingCaster.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ class SerializingCaster
88
{
99
public function get(?Model $model, string $key, mixed $value, array $attributes)
1010
{
11-
return unserialize($value);
11+
12+
return empty($value) ? $value : unserialize($value);
1213
}
1314

1415
public function set(?Model $model, string $key, mixed $value, array $attributes)
1516
{
16-
return serialize($value);
17+
return empty($value) ? $value :serialize($value);
1718
}
1819
}
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
namespace Matteoc99\LaravelPreference\Console\Commands;
4+
5+
use Illuminate\Console\Command;
6+
use Matteoc99\LaravelPreference\Enums\ApplicationVersion;
7+
8+
class PreferenceMigrate extends Command
9+
{
10+
/**
11+
* The name and signature of the console command.
12+
*
13+
* @var string
14+
*/
15+
protected $signature = 'preference:migrate:version';
16+
17+
/**
18+
* The console command description.
19+
*
20+
* @var string
21+
*/
22+
protected $description = 'Migrate incompatibilities ';
23+
24+
public function __construct()
25+
{
26+
parent::__construct();
27+
}
28+
29+
30+
public function handle()
31+
{
32+
$version_from = ApplicationVersion::from($this->choice(
33+
'From which version?',
34+
[ApplicationVersion::Version1->value],
35+
0,
36+
));
37+
38+
$version_to = ApplicationVersion::from($this->choice(
39+
'To which version?',
40+
[ApplicationVersion::Version2->value],
41+
0
42+
));
43+
44+
// handle future cases
45+
46+
}
47+
}

src/Enums/ApplicationVersion.php

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Matteoc99\LaravelPreference\Enums;
4+
5+
enum ApplicationVersion: string
6+
{
7+
case Version1 = '1.x';
8+
case Version2 = '2.x';
9+
}

src/Enums/Cast.php

+8-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
enum Cast: string implements CastableEnum
1717
{
18+
case NONE = 'none';
1819
case INT = 'int';
1920
case FLOAT = 'float';
2021
case STRING = 'string';
@@ -32,6 +33,7 @@ enum Cast: string implements CastableEnum
3233
public function validation(): ValidationRule|array|string|null
3334
{
3435
return match ($this) {
36+
self::NONE => null,
3537
self::INT => 'integer',
3638
self::FLOAT => 'numeric',
3739
self::STRING => 'string',
@@ -57,7 +59,7 @@ public function castFromString(string $value): mixed
5759
self::DATE, self::DATETIME => new Carbon($value),
5860
self::TIME => Carbon::now()->setTimeFromTimeString($value),
5961
self::TIMESTAMP => Carbon::createFromTimestamp($value),
60-
self::BACKED_ENUM, self::ENUM, self::OBJECT => unserialize($value),
62+
self::NONE, self::BACKED_ENUM, self::ENUM, self::OBJECT => unserialize($value),
6163
};
6264
}
6365

@@ -75,7 +77,7 @@ public function castToString(mixed $value): string
7577
self::DATETIME => $value->toDateTimeString(),
7678
self::TIMESTAMP => $value->timestamp,
7779
self::TIME => $value->toTimeString(),
78-
self::BACKED_ENUM, self::ENUM, self::OBJECT => serialize($value),
80+
self::NONE, self::BACKED_ENUM, self::ENUM, self::OBJECT => serialize($value),
7981
};
8082
}
8183

@@ -85,13 +87,13 @@ public function castToString(mixed $value): string
8587
private function ensureType(mixed $value): mixed
8688
{
8789
return match ($this) {
90+
self::NONE => $value,
8891
self::INT => intval($value),
8992
self::FLOAT => floatval($value),
9093
self::STRING => (string)$value,
9194
self::BOOL => !empty($value),
9295
self::ARRAY => $this->ensureArray($value),
93-
self::TIMESTAMP, self::DATETIME, self::DATE => $this->ensureCarbon($value),
94-
self::TIME => $this->ensureCarbon($value, 'setTimeFromTimeString'),
96+
self::TIMESTAMP, self::DATETIME, self::DATE, self::TIME => $this->ensureCarbon($value),
9597
self::BACKED_ENUM => $this->ensureBackedEnum($value),
9698
self::ENUM => $this->ensureEnum($value),
9799
self::OBJECT => $this->ensureObject($value),
@@ -111,11 +113,11 @@ private function ensureArray(mixed $value): array
111113
/**
112114
* @throws ValidationException
113115
*/
114-
private function ensureCarbon(mixed $value, string $method = 'parse'): Carbon
116+
private function ensureCarbon(mixed $value): Carbon
115117
{
116118
if (!($value instanceof Carbon)) {
117119
try {
118-
$value = Carbon::$method($value);
120+
$value = Carbon::parse($value);
119121
} catch (Exception $_) {
120122
throw ValidationException::withMessages([
121123
"Invalid format for cast to " . $this->name

src/Factory/PreferenceBuilder.php

+31-10
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44

55
use Illuminate\Contracts\Validation\ValidationRule;
66
use Illuminate\Database\Eloquent\Builder;
7+
use Illuminate\Validation\ValidationException;
78
use InvalidArgumentException;
8-
use Matteoc99\LaravelPreference\Casts\RuleCaster;
99
use Matteoc99\LaravelPreference\Casts\ValueCaster;
1010
use Matteoc99\LaravelPreference\Contracts\CastableEnum;
1111
use Matteoc99\LaravelPreference\Contracts\PreferenceGroup;
12+
use Matteoc99\LaravelPreference\Contracts\PreferencePolicy;
1213
use Matteoc99\LaravelPreference\Enums\Cast;
1314
use Matteoc99\LaravelPreference\Models\Preference;
1415
use Matteoc99\LaravelPreference\Utils\SerializeHelper;
16+
use Matteoc99\LaravelPreference\Utils\ValidationHelper;
1517

1618
class PreferenceBuilder
1719
{
@@ -56,6 +58,12 @@ private function withName(PreferenceGroup $name): static
5658
}
5759

5860

61+
public function withPolicy(PreferencePolicy $policy): static
62+
{
63+
$this->preference->policy = $policy;
64+
return $this;
65+
}
66+
5967
public function withDefaultValue(mixed $value): static
6068
{
6169
$this->preference->default_value = $value;
@@ -76,16 +84,25 @@ public function withRule(ValidationRule $rule): static
7684

7785
public function create(): Preference
7886
{
79-
$this->preference->save();
80-
return $this->preference;
87+
return $this->updateOrCreate();
8188
}
8289

8390
public function updateOrCreate(): Preference
8491
{
85-
$this->preference = Preference::updateOrCreate($this->preference->toArrayOnly(['name', 'group']));
86-
return $this->preference;
92+
if (isset($this->preference->default_value)) {
93+
ValidationHelper::validatePreference($this->preference);
94+
}
95+
96+
$this->preference = Preference::updateOrCreate(
97+
$this->preference->toArrayOnly(['name', 'group']),
98+
$this->preference->attributesToArray()
99+
);
100+
return $this->preference->fresh();
87101
}
88102

103+
/**
104+
* @throws ValidationException
105+
*/
89106
public static function initBulk(array $preferences): void
90107
{
91108
if (empty($preferences)) {
@@ -96,25 +113,27 @@ public static function initBulk(array $preferences): void
96113
if (empty($preferenceData['cast'])) {
97114
$preferenceData['cast'] = Cast::STRING;
98115
}
99-
100116
if (empty($preferenceData['name']) || !($preferenceData['name'] instanceof PreferenceGroup)) {
101117
throw new InvalidArgumentException(
102118
sprintf("index: #%s name is required and needs to be a PreferenceGroup", $key)
103119
);
104120
}
105-
106121
if (empty($preferenceData['cast']) || !($preferenceData['cast'] instanceof CastableEnum)) {
107122
throw new InvalidArgumentException(
108123
sprintf("index: #%s cast is required and needs to implement 'CastableEnum'", $key)
109124
);
110125
}
111-
112-
if (!empty($preferenceData['default_value']) && !empty($preferenceData['rule']) && !$preferenceData['rule']->passes('', $preferenceData['default_value'])) {
126+
if (!empty($preferenceData['rule']) && !$preferenceData['rule'] instanceof ValidationRule) {
113127
throw new InvalidArgumentException(
114-
sprintf("index: #%s default_value fails the validation rule", $key)
128+
sprintf("index: #%s validation rule musst implement ValidationRule", $key)
115129
);
116130
}
117131

132+
if (!empty($preferenceData['default_value'])) {
133+
ValidationHelper::validateValue($preferenceData['default_value'], $preferenceData['cast'], $preferenceData['rule']);
134+
}
135+
136+
118137
if (array_key_exists('group', $preferenceData)) {
119138
throw new InvalidArgumentException(
120139
sprintf("index: #%s group has been deprecated", $key)
@@ -170,4 +189,6 @@ public static function deleteBulk(array $preferences): int
170189

171190
return $query->delete();
172191
}
192+
193+
173194
}

src/Models/Preference.php

+13-35
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,23 @@
77
use Matteoc99\LaravelPreference\Casts\SerializingCaster;
88
use Matteoc99\LaravelPreference\Casts\ValueCaster;
99
use Matteoc99\LaravelPreference\Contracts\CastableEnum;
10+
use Matteoc99\LaravelPreference\Contracts\PreferencePolicy;
1011

1112

1213
/**
1314
* Class Preference
1415
*
1516
* @package Matteoc99\LaravelPreference\Models
16-
* @property int $id
17-
* @property string $group
18-
* @property string $name
19-
* @property string|null $description
20-
* @property CastableEnum|null $cast
21-
* @property ValidationRule|null $rule
22-
* @property mixed $default_value
23-
* @property Carbon $created_at
24-
* @property Carbon $updated_at
17+
* @property int $id
18+
* @property string $group
19+
* @property string $name
20+
* @property string|null $description
21+
* @property CastableEnum|null $cast
22+
* @property ValidationRule|null $rule
23+
* @property PreferencePolicy|null $policy
24+
* @property mixed $default_value
25+
* @property Carbon $created_at
26+
* @property Carbon $updated_at
2527
*/
2628
class Preference extends BaseModel
2729
{
@@ -33,6 +35,7 @@ class Preference extends BaseModel
3335
'name',
3436
'description',
3537
'cast',
38+
'policy',
3639
'rule',
3740
'default_value',
3841
];
@@ -42,33 +45,8 @@ class Preference extends BaseModel
4245
'updated_at' => 'datetime',
4346
'cast' => SerializingCaster::class,
4447
'rule' => SerializingCaster::class,
48+
'policy' => SerializingCaster::class,
4549
'default_value' => ValueCaster::class,
4650
];
4751

48-
public function getValidationRules(): array
49-
{
50-
$rules = [];
51-
if ($this->cast) {
52-
$castValidation = $this->cast->validation();
53-
if ($castValidation) {
54-
$rules = array_merge($rules, $this->processRule($castValidation));
55-
}
56-
}
57-
if ($this->rule) {
58-
$rules = array_merge($rules, $this->processRule($this->rule));
59-
}
60-
61-
return $rules;
62-
}
63-
private function processRule($rule): array
64-
{
65-
if (is_array($rule)) {
66-
return $rule;
67-
} elseif ($rule instanceof ValidationRule) {
68-
return [$rule];
69-
} else {
70-
return explode('|', $rule);
71-
}
72-
}
73-
7452
}

0 commit comments

Comments
 (0)