From abcc448db6c04f4e7f5af1c2b6c2f0f74aafabf4 Mon Sep 17 00:00:00 2001 From: Ben Sherred Date: Tue, 28 Nov 2023 20:07:00 +0000 Subject: [PATCH] refactor: prefixes to use class --- config/sqids.php | 26 ++++++++--------------- src/Concerns/HasSqids.php | 8 ------- src/Contracts/Prefix.php | 16 ++++++++++++++ src/Model.php | 11 +++++++--- src/Prefixes/ConstantPrefix.php | 29 ++++++++++++++++++++++++++ src/Prefixes/SimplePrefix.php | 27 ++++++++++++++++++++++++ src/Sqids.php | 30 +++++++++------------------ src/Support/Config.php | 30 ++++++++++++++------------- tests/Concerns/HasSqidsTest.php | 2 +- tests/Prefixes/ConstantPrefixTest.php | 16 ++++++++++++++ tests/Prefixes/DefaultPrefixTest.php | 16 ++++++++++++++ tests/SqidsTest.php | 4 ++-- tests/Support/ConfigTest.php | 26 ++++++----------------- 13 files changed, 156 insertions(+), 85 deletions(-) create mode 100644 src/Contracts/Prefix.php create mode 100644 src/Prefixes/ConstantPrefix.php create mode 100644 src/Prefixes/SimplePrefix.php create mode 100644 tests/Prefixes/ConstantPrefixTest.php create mode 100644 tests/Prefixes/DefaultPrefixTest.php diff --git a/config/sqids.php b/config/sqids.php index 1957c44..3e2826a 100644 --- a/config/sqids.php +++ b/config/sqids.php @@ -22,7 +22,7 @@ | Alphabet |-------------------------------------------------------------------------- | - | This option controls the default "alphabet" used for generating sqids. + | This option controls the default "alphabet" used for generating Sqids. | The characters and numbers listed below will be included. You must | have at least 3 unique characters or numbers. | @@ -35,7 +35,7 @@ | Length |-------------------------------------------------------------------------- | - | This option controls the "minimum length" of the generated sqid + | This option controls the "minimum length" of the generated Sqid | excluding the prefix and separator. This value must be greater | than 0. | @@ -49,7 +49,7 @@ |-------------------------------------------------------------------------- | | THis option allows you to "blacklist" certain words that shouldn't be - | included in the generated sqids. + | included in the generated Sqids. | */ @@ -61,7 +61,7 @@ |-------------------------------------------------------------------------- | | This option controls the "separator" between the prefix and the - | generatedsqid. + | generated Sqid. | */ @@ -69,23 +69,15 @@ /* |-------------------------------------------------------------------------- - | Prefix + | Prefix Class |-------------------------------------------------------------------------- | - | This option controls the sqid "prefix", You can control the length of - | the prefix and the casing. By default, the prefix will be generated - | based on the model name. - | - | Setting the prefix length to "0" will remove the prefix all together. - | - | Supported Casing: "lower", "upper", "camel", "snake", "kebab", - | "title", "studly" + | This option controls the class that should be used for generating the + | Sqid prefix. You can use any class that implements the following + | contract: \RedExplosion\Sqids\Contracts\Prefix. | */ - 'prefix' => [ - 'length' => 3, - 'case' => 'lower', - ], + 'prefix_class' => \RedExplosion\Sqids\Prefixes\ConstantPrefix::class, ]; diff --git a/src/Concerns/HasSqids.php b/src/Concerns/HasSqids.php index 9949223..80914e0 100644 --- a/src/Concerns/HasSqids.php +++ b/src/Concerns/HasSqids.php @@ -50,14 +50,6 @@ public function resolveRouteBindingQuery($query, $value, $field = null): Builder public static function keyFromSqid(string $sqid): ?int { - $prefixLength = Config::prefixLength(); - $prefix = Str::beforeLast(subject: $sqid, search: Config::separator()); - $expectedPrefix = Sqids::prefixForModel(model: __CLASS__); - - if ($prefixLength && $prefix !== $expectedPrefix) { - return null; - } - $sqid = Str::afterLast(subject: $sqid, search: Config::separator()); $length = Str::length(value: $sqid); diff --git a/src/Contracts/Prefix.php b/src/Contracts/Prefix.php new file mode 100644 index 0000000..222102a --- /dev/null +++ b/src/Contracts/Prefix.php @@ -0,0 +1,16 @@ + $model + * @return string + */ + public function prefix(string $model): string; +} diff --git a/src/Model.php b/src/Model.php index 02c1d28..afd1eef 100644 --- a/src/Model.php +++ b/src/Model.php @@ -77,9 +77,14 @@ protected static function models(): array /** @phpstan-ignore-next-line */ ->filter(fn(ReflectionClass $class) => in_array(needle: HasSqids::class, haystack: $class->getTraitNames())) /** @phpstan-ignore-next-line */ - ->mapWithKeys(fn(ReflectionClass $reflectionClass) => [ - Sqids::prefixForModel($reflectionClass->getName()) => $reflectionClass->getName() - ]) + ->mapWithKeys(function (ReflectionClass $reflectionClass): array { + /** @var class-string $model */ + $model = $reflectionClass->getName(); + + return [ + Sqids::prefixForModel($model) => $reflectionClass->getName(), + ]; + }) ->toArray(); return $models; diff --git a/src/Prefixes/ConstantPrefix.php b/src/Prefixes/ConstantPrefix.php new file mode 100644 index 0000000..cc669dd --- /dev/null +++ b/src/Prefixes/ConstantPrefix.php @@ -0,0 +1,29 @@ + $model + * @return string + */ + public function prefix(string $model): string + { + $classBasename = class_basename(class: $model); + + $prefix = str_replace(search: ['a', 'e', 'i', 'o', 'u'], replace: '', subject: $classBasename); + + $prefix = rtrim(mb_strimwidth(string: $prefix, start: 0, width: 3, encoding: 'UTF-8')); + + return Str::lower(value: $prefix); + } +} diff --git a/src/Prefixes/SimplePrefix.php b/src/Prefixes/SimplePrefix.php new file mode 100644 index 0000000..07351e7 --- /dev/null +++ b/src/Prefixes/SimplePrefix.php @@ -0,0 +1,27 @@ + $model + * @return string + */ + public function prefix(string $model): string + { + $classBasename = class_basename(class: $model); + + $prefix = rtrim(mb_strimwidth(string: $classBasename, start: 0, width: 3, encoding: 'UTF-8')); + + return Str::lower(value: $prefix); + } +} diff --git a/src/Sqids.php b/src/Sqids.php index db0c40a..1b745d7 100644 --- a/src/Sqids.php +++ b/src/Sqids.php @@ -5,7 +5,6 @@ namespace RedExplosion\Sqids; use Illuminate\Database\Eloquent\Model; -use Illuminate\Support\Str; use RedExplosion\Sqids\Support\Config; use Sqids\Sqids as SqidsCore; @@ -23,14 +22,13 @@ public static function forModel(Model $model): string return "{$prefix}{$separator}{$sqid}"; } + /** + * @param class-string $model + * @return string + */ public static function prefixForModel(string $model): ?string { - $classBasename = class_basename(class: $model); - $prefixLength = Config::prefixLength(); - - if (!$prefixLength) { - return null; - } + $prefixClass = Config::prefixClass(); /** @var string|null $modelPrefix */ /** @phpstan-ignore-next-line */ @@ -40,19 +38,11 @@ public static function prefixForModel(string $model): ?string return $modelPrefix; } - $prefix = $prefixLength < 0 - ? $classBasename - : rtrim(mb_strimwidth(string: $classBasename, start: 0, width: $prefixLength, encoding: 'UTF-8')); - - return match (Config::prefixCase()) { - 'upper' => Str::upper(value: $prefix), - 'camel' => Str::camel(value: $prefix), - 'snake' => Str::snake(value: $prefix), - 'kebab' => Str::kebab(value: $prefix), - 'title' => Str::title(value: $prefix), - 'studly' => Str::studly(value: $prefix), - default => Str::lower(value: $prefix), - }; + if (!$prefixClass) { + return null; + } + + return $prefixClass->prefix(model: $model); } public static function encodeId(string $model, int $id): string diff --git a/src/Support/Config.php b/src/Support/Config.php index 9dec0fa..7e5cc45 100644 --- a/src/Support/Config.php +++ b/src/Support/Config.php @@ -4,6 +4,10 @@ namespace RedExplosion\Sqids\Support; +use Exception; +use RedExplosion\Sqids\Contracts\Prefix; +use RedExplosion\Sqids\Prefixes\SimplePrefix; + class Config { protected static string $defaultAlphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; @@ -74,26 +78,24 @@ public static function separator(): string return $separator; } - public static function prefixLength(): int + public static function prefixClass(): ?Prefix { - /** @var int|null $prefixLength */ - $prefixLength = config(key: 'sqids.prefix.length', default: static::$defaultPrefixLength); + $prefix = config(key: 'sqids.prefix_class'); - if (!$prefixLength || !is_int($prefixLength)) { - return static::$defaultPrefixLength; + if (!$prefix) { + return null; } - return $prefixLength; - } - - public static function prefixCase(): string - { - $prefixCase = config(key: 'sqids.prefix.case', default: static::$defaultPrefixCase); + try { + $prefix = new $prefix(); + } catch (Exception) { + return new SimplePrefix(); + } - if (!$prefixCase || !is_string(value: $prefixCase)) { - return static::$defaultPrefixCase; + if (!$prefix instanceof Prefix) { + return new SimplePrefix(); } - return $prefixCase; + return new $prefix(); } } diff --git a/tests/Concerns/HasSqidsTest.php b/tests/Concerns/HasSqidsTest.php index f60b219..a32f621 100644 --- a/tests/Concerns/HasSqidsTest.php +++ b/tests/Concerns/HasSqidsTest.php @@ -11,7 +11,7 @@ expect($customer->sqid) ->toBeString() - ->toStartWith('cus_'); + ->toStartWith('cst_'); }); it('can get the sqid prefix', function (): void { diff --git a/tests/Prefixes/ConstantPrefixTest.php b/tests/Prefixes/ConstantPrefixTest.php new file mode 100644 index 0000000..700bd72 --- /dev/null +++ b/tests/Prefixes/ConstantPrefixTest.php @@ -0,0 +1,16 @@ +prefix(model: Customer::class) + ->toBe(expected: 'cst') + ->prefix(model: Charge::class) + ->toBe(expected: 'chr'); + ; +}); diff --git a/tests/Prefixes/DefaultPrefixTest.php b/tests/Prefixes/DefaultPrefixTest.php new file mode 100644 index 0000000..6c0eddf --- /dev/null +++ b/tests/Prefixes/DefaultPrefixTest.php @@ -0,0 +1,16 @@ +prefix(model: Customer::class) + ->toBe(expected: 'cus') + ->prefix(model: Charge::class) + ->toBe(expected: 'cha'); + ; +}); diff --git a/tests/SqidsTest.php b/tests/SqidsTest.php index 11ade80..57f3f79 100644 --- a/tests/SqidsTest.php +++ b/tests/SqidsTest.php @@ -14,7 +14,7 @@ expect(Sqids::forModel(model: $customer)) ->toBeString() - ->toStartWith('cus_'); + ->toStartWith('cst_'); }); it('can get the sqid prefix for a model', function (): void { @@ -24,7 +24,7 @@ expect($customer->getSqidPrefix()) ->toBeNull() ->and(Sqids::prefixForModel(model: $customer::class)) - ->toBe('cus') + ->toBe('cst') ->and($charge->getSqidPrefix()) ->toBe('ch') ->and(Sqids::prefixForModel(model: $charge::class)) diff --git a/tests/Support/ConfigTest.php b/tests/Support/ConfigTest.php index 9b1f25b..1c93e78 100644 --- a/tests/Support/ConfigTest.php +++ b/tests/Support/ConfigTest.php @@ -2,6 +2,8 @@ declare(strict_types=1); +use RedExplosion\Sqids\Prefixes\ConstantPrefix; +use RedExplosion\Sqids\Prefixes\SimplePrefix; use RedExplosion\Sqids\Support\Config; it('can get the alphabet', function (): void { @@ -52,26 +54,10 @@ expect(Config::separator())->toBe('_'); }); -it('can get the prefix length', function (): void { - expect(Config::prefixLength())->toBe(3); +it('can get the prefix class', function (): void { + expect(Config::prefixClass())->toBeInstanceOf(ConstantPrefix::class); - config()->set('sqids.prefix.length', 4); + config()->set('sqids.prefix_class', SimplePrefix::class); - expect(Config::prefixLength())->toBe(4); - - config()->set('sqids.prefix.length', '1'); - - expect(Config::prefixLength())->toBe(3); -}); - -it('can get the prefix case', function (): void { - expect(Config::prefixCase())->toBe('lower'); - - config()->set('sqids.prefix.case', 'upper'); - - expect(Config::prefixCase())->toBe('upper'); - - config()->set('sqids.prefix.case', 4); - - expect(Config::prefixCase())->toBe('lower'); + expect(Config::prefixClass())->toBeInstanceOf(SimplePrefix::class); });