PhpDev.App
deligoez/laravel-model-hashid

deligoez/laravel-model-hashid

Stars: 131

Forks: 16

Pull Requests: 13

Issues: 6

Watchers: 3

Last Updated: 2023-08-24 10:32:14

#️⃣ Generate, Save, and Route Stripe/Youtube-like Hash IDs for Laravel Eloquent Models

License:

Languages: PHP

Latest Version on Packagist Total Downloads Packagist
Maintainability Rating Reliability Rating Security Rating
Coverage Lines of Code Bugs
Technical Debt Vulnerabilities Code Smells Duplicated Lines (%)
tests code style types Quality Gate Status
Open Source Love

Using this package you can generate, save and, route Stripe-like Hash Ids for your Eloquent Models.

Hash Ids are short, unique, and non-sequential, and can generate unique Ids for URLs and hide database row numbers from the user. For more information about Hash Ids please visit hashids.org.

With this package, you can customize Hash Id generation and add a model prefix and also separator.

For a User model with an id of 1234, you can generate Hash Ids like user_kqYZeLgo.

So instead of;
https://your-endpoint.com/user/1234

You can have endpoints like;
https://your-endpoint.com/user/user_kqYZeLgo

You have complete control over your Hash Id length and style. Check out the configuration file for more options.

Table of contents

Features

  • Customizable Hash Id Generation
    • Hash Id Salt
    • Length
    • HashID Alphabet
    • Model Prefix Length and Case
    • Separator
  • Model Specific Hash Id Generation
    • User-defined prefix per Model (optional)
    • Define separate configurations per Model
  • Route (Model) Binding using Hash Ids (optional)
  • Automatically save Hash Ids to the database (optional)

Compatibility Table

The table below shows the compatibility across Laravel, PHP, and this package's current version.

Package Version Laravel version PHP version Compatible
^2.0 9.* , 10.* 8.1.*
^1.0 8.* 8.0.*
8.* 7.4.*
8.* 7.3.*
7.x *

Installation

  1. Install via Composer:
    composer require deligoez/laravel-model-hashid
  2. Publish the config file:
    php artisan vendor:publish --provider="Deligoez\LaravelModelHashId\LaravelModelHashIdServiceProvider" --tag="config"

Usage

Model Hash Id Generation

Add the HasHashId trait to any Laravel Model that should use Hash Ids.

use Illuminate\Database\Eloquent\Model;
use Deligoez\LaravelModelHashId\Traits\HasHashId;

class ModelA extends Model
{
    use HasHashId;
    
    ...
}

Model Attributes and Static Model Functions

You will be able to use hashId and hashIdRaw attributes and keyFromHashId() static model function.

$modelA = ModelA::find(1234);
$modelA->hashId;    // model_a_kqYZeLgo
$modelA->hashIdRaw; // kqYZeLgo

ModelA::keyFromHashId('model_a_kqYZeLgo') // 1234

Query Builder Functions

You can use all finding related Laravel query builder functions with Hash Ids.

// Find a model by its Hash Id.
ModelA::findByHashId('model_a_kqYZeLgo');

// Find multiple models by their Hash Ids.
ModelA::findManyByHashId(['model_a_kqYZeLgo', 'model_a_ZeLgokqY']);

// Find a model by its Hash Id or throw an exception.
ModelA::findOrFailByHashId('model_a_kqYZeLgo');

// Find a model by its Hash Id or or call a callback.
ModelA::findOrByHashId('model_a_kqYZeLgo');

// Find a model by its Hash Id or return fresh model instance.
ModelA::findOrNewByHashId('model_a_kqYZeLgo');

// Add a where clause on the Hash Id to the query.
ModelA::whereHashId('model_a_kqYZeLgo');

// Add a where not clause on the Hash Id to the query.
ModelA::whereHashIdNot('model_a_kqYZeLgo');

Routing and Route Model Binding (Optional)

Simply add the HasHashIdRouting trait to your model that you want to route using Hash Ids.

use Illuminate\Database\Eloquent\Model;
use Deligoez\LaravelModelHashId\Traits\HasHashIdRouting;

class ModelA extends Model
{
    use HasHashIdRouting;
    
    ...
}

Route Model Binding (Implicit)

You can define a route and/or controller method like this by Laravel conventions.

// You can call this route with a Hash Id: your-endpoint.com/model-a/model_a_kqYZeLgo
Route::get('/model-a/{modelA}', function (ModelA $modelA) {
    // Your ModelA instance
    $modelA;
});

Route Model Binding (Explicit)

You can also define a custom model key on your RouteServiceProvider.

Route::model('hash_id', ModelA::class);
// You can call this route with a Hash Id: your-endpoint.com/model-a/model_a_kqYZeLgo
Route::get('/model-a/{hash_id}', function ($modelBinding) {
    // Your ModelA instance
    $modelBinding;
});

Saving Hash Ids to the Database (Optional)

You can add the SavesHashId Trait to any of your Laravel Model that should save the generated Hash Ids.

After that, you should set database_column setting in the configuration file. You can define database_column setting per model separately or for all of your models.

use Illuminate\Database\Eloquent\Model;
use Deligoez\LaravelModelHashId\Traits\SavesHashId;

class ModelA extends Model
{
    use SavesHashId;
    
    ...
}

All Hash Id generation or decoding methods work ON-THE-FLY. So you DO NOT need to save Hash Ids to the database for that reason.

Since generating a Hash Id requires an integer model id/key, remember that storing the Hash Ids to a database will result in an additional database query.

Hash Id Terminology

A typical Hash Id consist of 3 parts.

  • Model Prefix (model_a)
  • Separator (_)
  • Raw Hash Id (kqYZeLgo)

Model Prefix and Separator are OPTIONAL. You can generate Hash Ids that only contains Raw Hash Ids.

Configuration

This is the contents of the published config file:

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Salt String
    |--------------------------------------------------------------------------
    |
    | This salt string is used for generating HashIDs and should be set
    | to a random string, otherwise these generated HashIDs will not be
    | safe. Please do this definitely before deploying your application!
    |
    */

    'salt' => env('HASHID_SALT', 'your-secret-salt-string'),

    /*
    |--------------------------------------------------------------------------
    | Raw HashID Length
    |--------------------------------------------------------------------------
    |
    | This is the length of the raw HashID. The model prefix, separator
    | and the raw HashID are combined all together. So the Model HashID
    | length is the sum of raw HashID, separator, and model prefix lengths.
    |
    | Default: 13
    |
    */

    'length' => 13,

    /*
    |--------------------------------------------------------------------------
    | HashID Alphabet
    |--------------------------------------------------------------------------
    |
    | This alphabet will generate raw HashIDs. Please keep in mind that it
    | must contain at least 16 unique characters and can't contain spaces.
    |
    | Default: 'abcdefghjklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ234567890'
    |
    */

    'alphabet' => 'abcdefghjklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ234567890',

    /*
    |--------------------------------------------------------------------------
    | Model Prefix Length
    |--------------------------------------------------------------------------
    |
    | Here you can specify the length of the model prefix. By default, they
    | will generate it from the first letters of short class name.
    | Set it -1 to use full short class name as prefix.
    | Set it 0 to not use any prefix at all.
    |
    | Default: 3
    |
    */

    'prefix_length' => 3,

    /*
    |--------------------------------------------------------------------------
    | Model Prefix Case
    |--------------------------------------------------------------------------
    |
    | Here you can set the case of the prefix. Please keep in mind that for
    | some prefix cases, underscore (‘_’) characters will be added to the
    | prefix if your model name is multi word.
    |
    | Default: 'lower'
    |
    | Supported: "lower", "upper", "camel", "snake", "kebab",
    |            "title", "studly", "plural_studly"
    |
    */

    'prefix_case' => 'lower',

    /*
    |--------------------------------------------------------------------------
    | HashID Model Prefix Separator
    |--------------------------------------------------------------------------
    |
    | Here you can set the separator for your HashIDs. The separator
    | will be added between model prefix and the raw HashID.
    |
    | Default: '_'
    |
    */

    'separator' => '_',

    /*
    |--------------------------------------------------------------------------
    | HashID Database Column
    |--------------------------------------------------------------------------
    |
    | By using `SavesHashIDs` trait, you can save model HashIDs to database.
    | Here you can set the database column name for HashIDs to save.
    |
    | Default: 'hash_id'
    |
    */

    'database_column' => 'hash_id',

    /*
    |--------------------------------------------------------------------------
    | Model Specific Generators
    |--------------------------------------------------------------------------
    |
    | Here you can set specific HashID generators for individual Models.
    | Each one of the setting above can be defined per model. You can
    | see an example below as a comment.
    |
    */

    'model_generators' => [
        // App\Models\User::class => [
        //     'salt'            => 'your-model-specific-salt-string',
        //     'length'          => 13,
        //     'alphabet'        => 'abcdefghjklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ234567890',
        //     'prefix_length'   => 3,
        //     'prefix_case'     => 'lower',
        //     'separator'       => '_',
        //     'database_column' => 'hash_id',
        // ],

        // App\Models\Post::class => [
        //     'salt'            => 'your-model-specific-salt-string',
        //     'length'          => 13,
        //     'alphabet'        => 'abcdefghjklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ234567890', 
        //     'prefix'          => 'abc', // Custom prefix that is not auto-generated
        //     'separator'       => '_',
        // ],
    ],
];

Roadmap

  • Custom Model Prefixes (Not generated from a Model name) (Thanks to @plunkettscott)
  • Hash Id Validation Rules
  • Generic Generators (Not bound to a Laravel Model)

Testing

composer test

Used By

This project is used by the following companies/products, you can add yours too:

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.