├── .php_cs.dist.php ├── .phpunit.cache └── test-results ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── config └── prefixed-ids.php └── src ├── Exceptions ├── NoPrefixConfiguredForModel.php └── NoPrefixedModelFound.php ├── Models └── Concerns │ └── HasPrefixedId.php ├── PrefixedIds.php └── PrefixedIdsServiceProvider.php /.php_cs.dist.php: -------------------------------------------------------------------------------- 1 | in([ 5 | __DIR__ . '/src', 6 | __DIR__ . '/tests', 7 | ]) 8 | ->name('*.php') 9 | ->notName('*.blade.php') 10 | ->ignoreDotFiles(true) 11 | ->ignoreVCS(true); 12 | 13 | return (new PhpCsFixer\Config()) 14 | ->setRules([ 15 | '@PSR12' => true, 16 | 'array_syntax' => ['syntax' => 'short'], 17 | 'ordered_imports' => ['sort_algorithm' => 'alpha'], 18 | 'no_unused_imports' => true, 19 | 'not_operator_with_successor_space' => true, 20 | 'trailing_comma_in_multiline' => true, 21 | 'phpdoc_scalar' => true, 22 | 'unary_operator_spaces' => true, 23 | 'binary_operator_spaces' => true, 24 | 'blank_line_before_statement' => [ 25 | 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'], 26 | ], 27 | 'phpdoc_single_line_var_spacing' => true, 28 | 'phpdoc_var_without_name' => true, 29 | 'class_attributes_separation' => [ 30 | 'elements' => [ 31 | 'method' => 'one', 32 | ], 33 | ], 34 | 'method_argument_space' => [ 35 | 'on_multiline' => 'ensure_fully_multiline', 36 | 'keep_multiple_spaces_after_comma' => true, 37 | ], 38 | 'single_trait_insert_per_statement' => true, 39 | ]) 40 | ->setFinder($finder); 41 | -------------------------------------------------------------------------------- /.phpunit.cache/test-results: -------------------------------------------------------------------------------- 1 | {"version":1,"defects":[],"times":{"Spatie\\PrefixedIds\\Tests\\PrefixedIdsTest::it_generates_prefixed_id_using_method":0.02,"Spatie\\PrefixedIds\\Tests\\PrefixedIdsTest::it_will_generate_unique_ids_using_method":0.002,"Spatie\\PrefixedIds\\Tests\\PrefixedIdsTest::it_can_generated_a_prefixed_it":0,"Spatie\\PrefixedIds\\Tests\\PrefixedIdsTest::it_will_generate_unique_ids":0,"Spatie\\PrefixedIds\\Tests\\PrefixedIdsTest::a_model_can_find_a_record_with_the_given_prefixed_id":0.002,"Spatie\\PrefixedIds\\Tests\\PrefixedIdsTest::it_will_throw_an_exception_if_the_model_is_not_configured":0.001,"Spatie\\PrefixedIds\\Tests\\PrefixedIdsTest::it_can_find_the_right_model_for_the_given_prefixed_id":0.001,"Spatie\\PrefixedIds\\Tests\\PrefixedIdsTest::a_model_can_find_or_fail_a_record_with_the_given_prefixed_id":0,"Spatie\\PrefixedIds\\Tests\\PrefixedIdsTest::it_throws_exception_on_invalid_prefixed_id":0.001}} -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `laravel-prefixed-ids` will be documented in this file. 4 | 5 | ## 1.4.1 - 2025-02-17 6 | 7 | ### What's Changed 8 | 9 | * Laravel 12.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-prefixed-ids/pull/21 10 | 11 | **Full Changelog**: https://github.com/spatie/laravel-prefixed-ids/compare/1.4.0...1.4.1 12 | 13 | ## 1.4.0 - 2024-03-06 14 | 15 | ### What's Changed 16 | 17 | * Laravel 11.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-prefixed-ids/pull/20 18 | 19 | **Full Changelog**: https://github.com/spatie/laravel-prefixed-ids/compare/1.3.1...1.4.0 20 | 21 | ## 1.3.1 - 2023-01-24 22 | 23 | ### What's Changed 24 | 25 | - Laravel 10.x Compatibility by @laravel-shift in https://github.com/spatie/laravel-prefixed-ids/pull/16 26 | 27 | ### New Contributors 28 | 29 | - @laravel-shift made their first contribution in https://github.com/spatie/laravel-prefixed-ids/pull/16 30 | 31 | **Full Changelog**: https://github.com/spatie/laravel-prefixed-ids/compare/1.3.0...1.3.1 32 | 33 | ## 1.2.0 - 2022-01-13 34 | 35 | - allow Laravel 9 36 | 37 | ## 1.1.0 - 2021-10-01 38 | 39 | - add findOrFail (#11) 40 | 41 | ## 1.0.2 - 2021-02-25 42 | 43 | - fix solution descriptions 44 | 45 | ## 1.0.1 - 2021-02-25 46 | 47 | - fix solution descriptions 48 | 49 | ## 1.0.0 - 2021-02-24 50 | 51 | - initial release 52 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) spatie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Friendly prefixed IDs for Laravel models 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/laravel-prefixed-ids.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-prefixed-ids) 4 | [![run-tests](https://github.com/spatie/laravel-prefixed-ids/actions/workflows/run-tests.yml/badge.svg)](https://github.com/spatie/laravel-prefixed-ids/actions/workflows/run-tests.yml) 5 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/laravel-prefixed-ids.svg?style=flat-square)](https://packagist.org/packages/spatie/laravel-prefixed-ids) 6 | 7 | Prefixing an id will help users to recognize what kind of id it is. Stripe does this by default: customer ids are prefixed with `cus`, secret keys in production are prefixed with `sk_live_`, secret keys of a testing environment with `sk_test_` [and so on...](https://gist.github.com/fnky/76f533366f75cf75802c8052b577e2a5). 8 | 9 | This package can generate such friendly prefixed ids for Eloquent models. Here's how such generated ids could look like. 10 | 11 | ``` 12 | user_fj39fj3lsmxlsl 13 | test_token_dvklms109dls 14 | ``` 15 | 16 | The package can retrieve the model for a given prefixed id. 17 | 18 | ```php 19 | // on a specific model 20 | User::findByPrefixedId('user_fj39fj3lsmxlsl'); // returns a User model or `null` 21 | User::findByPrefixedIdOrFail('user_fj39fj3lsmxlsl'); // returns a User model or throws `NoPrefixedModelFound` 22 | 23 | // automatically determine the model of a given prefixed id 24 | $user = PrefixedIds::getModelClass('user_fj39fj3lsmxlsl') // returns the right model for the id or `null`; 25 | ``` 26 | 27 | ## Support us 28 | 29 | [](https://spatie.be/github-ad-click/laravel-prefixed-ids) 30 | 31 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 32 | 33 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 34 | 35 | ## Installation 36 | 37 | You can install the package via composer: 38 | 39 | ```bash 40 | composer require spatie/laravel-prefixed-ids 41 | ``` 42 | 43 | ### Preparing your models 44 | 45 | On each model that needs a prefixed id, you should use the `Spatie\PrefixedIds\Models\Concerns\HasPrefixedId` trait. 46 | 47 | ```php 48 | namespace App\Models; 49 | 50 | use Illuminate\Database\Eloquent\Model; 51 | use Spatie\PrefixedIds\Models\Concerns\HasPrefixedId; 52 | 53 | class YourModel extends Model 54 | { 55 | use HasPrefixedId; 56 | } 57 | ``` 58 | 59 | ### Preparing the database 60 | 61 | For each model that needs a prefixed id, you'll need to write a migration to add a `prefixed_id` column to its underlying table. 62 | 63 | If you wish to use another attribute name, you should publish the config file (see below) and set the `prefixed_id_attribute_name` config value to the attribute name of your liking. 64 | 65 | ```php 66 | Schema::create('your_models_table', function (Blueprint $table) { 67 | $table->string('prefixed_id')->nullable()->unique(); 68 | }); 69 | ``` 70 | 71 | ### Registering models with prefixed ids 72 | 73 | To register your models, you should pass the desired prefix and the class name of your model to `PrefixedIds::registerModels`. 74 | 75 | ```php 76 | Spatie\PrefixedIds\PrefixedIds::registerModels([ 77 | 'your_prefix_' => YourModel::class, 78 | 'another_prefix' => AnotherModel::class, 79 | ]); 80 | ``` 81 | 82 | Typically, you would put the code above in a service provider. 83 | 84 | ### Publish the config file 85 | 86 | Optionally, You can publish the config file with: 87 | 88 | ```bash 89 | php artisan vendor:publish --provider="Spatie\PrefixedIds\PrefixedIdsServiceProvider" --tag="prefixed-ids-config" 90 | ``` 91 | 92 | This is the contents of the published config file: 93 | 94 | ```php 95 | return [ 96 | /* 97 | * The attribute name used to store prefixed ids on a model 98 | */ 99 | 'prefixed_id_attribute_name' => 'prefixed_id', 100 | ]; 101 | ``` 102 | 103 | ## Usage 104 | 105 | When a model is created, it will automatically have a unique, prefixed id in the `prefixed_id` attribute. 106 | 107 | ```php 108 | $model = YourModel::create(); 109 | $model->prefixed_id // returns a random id like `your_model_fekjlmsme39dmMS` 110 | ``` 111 | 112 | ### Finding a specific model 113 | 114 | You can find the model with a given prefix by calling `findByPrefixedId` on it. 115 | 116 | ```php 117 | YourModel::findByPrefixedId('your_model_fekjlmsme39dmMS'); // returns an instance of `YourModel` 118 | YourModel::findByPrefixedId('non-existing-id'); // returns null 119 | YourModel::findByPrefixedIdOrFail('non-existing-id'); // throws `NoPrefixedModelFound` 120 | ``` 121 | 122 | ### Finding across models 123 | 124 | You can call `find` on `Spatie\PrefixedIds\PrefixedIds` to automatically get the right model for any given prefixed id. 125 | 126 | ```php 127 | $yourModel = Spatie\PrefixedIds\PrefixedIds::find('your_model_fekjlmsme39dmMS'); // returns an instance of `YourModel` or `null` 128 | $otherModel = Spatie\PrefixedIds\PrefixedIds::find('other_model_3Fjmmfsmls'); // returns an instance of `OtherModel` or `null` 129 | $otherModel = Spatie\PrefixedIds\PrefixedIds::findOrFail('other_model_3Fjmmfsmls'); // returns an instance of `OtherModel` or throws `NoPrefixedModelFound` 130 | ``` 131 | 132 | ### Customizing the unique ID generated 133 | 134 | You can use the function `Spatie\PrefixedIds\PrefixedIds::generateUniqueIdUsing()` to pass in a function to generate the unique ID. By default the library will use `Str::uuid()` to generate the ID. 135 | 136 | ```php 137 | // generate a unique Id with a set length 138 | Spatie\PrefixedIds\PrefixedIds::generateUniqueIdUsing(function(){ 139 | $length = 8; 140 | return substr(md5(uniqid(mt_rand(), true)), 0, $length); 141 | }); 142 | ``` 143 | 144 | ## Using the prefixed ids in your routes 145 | 146 | To use the prefixed ids in your routes, you'll have to add the `getRouteKeyName` method to your model. It should return the name of the attribute that holds the prefixed id. 147 | 148 | ```php 149 | public function getRouteKeyName() 150 | { 151 | return 'prefixed_id'; 152 | } 153 | ``` 154 | 155 | With this in place a route defined as... 156 | 157 | ```php 158 | Route::get('/api/your-models/{yourModel}', YourModelController::class)` 159 | ``` 160 | 161 | ... can be invoked with an URL like `/api/your-models/your_model_fekjlmsme39dmMS`. 162 | 163 | You'll find more info on route model binding in [the Laravel docs](https://laravel.com/docs/master/routing#route-model-binding). 164 | 165 | ## Testing 166 | 167 | ```bash 168 | composer test 169 | ``` 170 | 171 | ## Changelog 172 | 173 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 174 | 175 | ## Contributing 176 | 177 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 178 | 179 | ## Security Vulnerabilities 180 | 181 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 182 | 183 | ## Credits 184 | 185 | - [Freek Van der Herten](https://github.com/freekmurze) 186 | - [All Contributors](../../contributors) 187 | 188 | This package is inspired by [excid3/prefixed_ids](https://github.com/excid3/prefixed_ids) 189 | 190 | ## License 191 | 192 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 193 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/laravel-prefixed-ids", 3 | "description": "Friendly prefixed IDs for Laravel models", 4 | "keywords": [ 5 | "spatie", 6 | "laravel-prefixed-ids" 7 | ], 8 | "homepage": "https://github.com/spatie/laravel-prefixed-ids", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Freek Van der Herten", 13 | "email": "freek@spatie.be", 14 | "role": "Developer" 15 | } 16 | ], 17 | "require": { 18 | "php": "^8.2", 19 | "facade/ignition-contracts": "^1.0", 20 | "illuminate/contracts": "^10.0|^11.0|^12.0", 21 | "spatie/laravel-package-tools": "^1.9" 22 | }, 23 | "require-dev": { 24 | "orchestra/testbench": "^8.0|^9.0|^10.0", 25 | "phpunit/phpunit": "^10.5|^11.5.3", 26 | "spatie/laravel-ray": "^1.9" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Spatie\\PrefixedIds\\": "src" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "Spatie\\PrefixedIds\\Tests\\": "tests", 36 | "Spatie\\PrefixedIds\\Tests\\Database\\Migrations\\": "tests/database/migrations" 37 | } 38 | }, 39 | "scripts": { 40 | "test": "vendor/bin/phpunit --colors=always", 41 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage" 42 | }, 43 | "config": { 44 | "sort-packages": true, 45 | "allow-plugins": { 46 | "composer/package-versions-deprecated": true 47 | } 48 | }, 49 | "minimum-stability": "dev", 50 | "prefer-stable": true, 51 | "extra": { 52 | "laravel": { 53 | "providers": [ 54 | "Spatie\\PrefixedIds\\PrefixedIdsServiceProvider" 55 | ] 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /config/prefixed-ids.php: -------------------------------------------------------------------------------- 1 | 'prefixed_id', 8 | ]; 9 | -------------------------------------------------------------------------------- /src/Exceptions/NoPrefixConfiguredForModel.php: -------------------------------------------------------------------------------- 1 | setSolutionDescription("You should register your model using `Spatie\PrefixedIds\PrefixedIds::registerModel()` in a service provider") 24 | ->setDocumentationLinks([ 25 | 'Documentation' => 'https://github.com/spatie/laravel-prefixed-ids#registering-models-with-prefixed-ids', 26 | ]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Exceptions/NoPrefixedModelFound.php: -------------------------------------------------------------------------------- 1 | setSolutionDescription("You should make sure your model has a valid `prefixed_id`") 21 | ->setDocumentationLinks([ 22 | 'Documentation' => 'https://github.com/spatie/laravel-prefixed-ids#usage', 23 | ]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Models/Concerns/HasPrefixedId.php: -------------------------------------------------------------------------------- 1 | {$attributeName} = $model->generatePrefixedId(); 18 | }); 19 | } 20 | 21 | public function getPrefixedIdAttribute(): ?string 22 | { 23 | $attributeName = config('prefixed-ids.prefixed_id_attribute_name'); 24 | 25 | return $this->attributes[$attributeName] ?? null; 26 | } 27 | 28 | public static function findByPrefixedId(string $prefixedId): ?Model 29 | { 30 | $attributeName = config('prefixed-ids.prefixed_id_attribute_name'); 31 | 32 | return static::firstWhere($attributeName, $prefixedId); 33 | } 34 | 35 | public static function findByPrefixedIdOrFail(string $prefixedId): Model 36 | { 37 | if (is_null($model = static::findByPrefixedId($prefixedId))) { 38 | throw NoPrefixedModelFound::make($prefixedId); 39 | } 40 | 41 | return $model; 42 | } 43 | 44 | protected function getIdPrefix(): string 45 | { 46 | $prefix = PrefixedIds::getPrefixForModel(static::class); 47 | 48 | if (! $prefix) { 49 | throw NoPrefixConfiguredForModel::make($this); 50 | } 51 | 52 | return $prefix; 53 | } 54 | 55 | protected function generatePrefixedId(): string 56 | { 57 | return "{$this->getIdPrefix()}{$this->getUniquePartForPrefixId()}"; 58 | } 59 | 60 | protected function getUniquePartForPrefixId(): string 61 | { 62 | return PrefixedIds::getUniqueId(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/PrefixedIds.php: -------------------------------------------------------------------------------- 1 | $model) { 19 | self::registerModel($prefix, $model); 20 | } 21 | } 22 | 23 | public static function registerModel(string $prefix, string $modelClass): void 24 | { 25 | static::$registeredModels[$prefix] = $modelClass; 26 | } 27 | 28 | public static function clearRegisteredModels(): void 29 | { 30 | static::$registeredModels = []; 31 | } 32 | 33 | public static function getPrefixForModel(string $modelClass): ?string 34 | { 35 | $keyedByModelClass = array_flip(static::$registeredModels); 36 | 37 | return $keyedByModelClass[$modelClass] ?? null; 38 | } 39 | 40 | public static function find(string $prefixedId): ?Model 41 | { 42 | if (! $modelClass = static::getModelClass($prefixedId)) { 43 | return null; 44 | } 45 | 46 | return $modelClass::findByPrefixedId($prefixedId); 47 | } 48 | 49 | public static function findOrFail(string $prefixedId): ?Model 50 | { 51 | if (! $modelClass = static::getModelClass($prefixedId)) { 52 | throw NoPrefixedModelFound::make($prefixedId); 53 | } 54 | 55 | return $modelClass::findByPrefixedIdOrFail($prefixedId); 56 | } 57 | 58 | public static function getModelClass(string $prefixedId): ?string 59 | { 60 | foreach (static::$registeredModels as $prefix => $modelClass) { 61 | if (str_starts_with($prefixedId, $prefix)) { 62 | return $modelClass; 63 | } 64 | } 65 | 66 | return null; 67 | } 68 | 69 | public static function getUniqueId(): string 70 | { 71 | if (! static::$generateUniqueIdUsing) { 72 | return str_replace('-', '', Str::uuid()); 73 | } 74 | 75 | return (static::$generateUniqueIdUsing)(); 76 | } 77 | 78 | public static function generateUniqueIdUsing(?Closure $generateUniqueIdUsing): void 79 | { 80 | static::$generateUniqueIdUsing = $generateUniqueIdUsing; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/PrefixedIdsServiceProvider.php: -------------------------------------------------------------------------------- 1 | name('laravel-prefixed-ids') 14 | ->hasConfigFile(); 15 | } 16 | } 17 | --------------------------------------------------------------------------------