Skip to content

Instantly share code, notes, and snippets.

@TheNodi
Last active June 15, 2023 23:18
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save TheNodi/3a69c00e485ebcfee569a7476193d36e to your computer and use it in GitHub Desktop.
Save TheNodi/3a69c00e485ebcfee569a7476193d36e to your computer and use it in GitHub Desktop.
Laravel Model Bindings with Hashids
<?php
namespace App;
use Vinkla\Hashids\HashidsManager;
/**
* Bind a model to a route based on the hash of
* its id (or other specified key).
*
* @package App
*
* @mixin \Illuminate\Database\Eloquent\Model
*/
trait HashidsRoutable
{
/**
* Instantiate appropriate Hashids connection
*
* @return \Hashids\Hashids
*/
protected function getHashidsInstance()
{
return app(HashidsManager::class)->connection($this->getHashidsConnection());
}
/**
* Determine Hashids connection to use
*
* @return null|string
*/
protected function getHashidsConnection()
{
return null;
}
/**
* Encode a parameter
*
* @param int $parameter
* @return string
*/
protected function encodeParameter($parameter)
{
return $this->getHashidsInstance()->encode($parameter);
}
/**
* Decode parameter
*
* @param string $parameter
* @return null|int Decoded value or null on failure
*/
protected function decodeParameter($parameter)
{
if (count($decoded = $this->getHashidsInstance()->decode($parameter)) != 1) {
// We are expecting a single value from the decode parameter,
// if none or multiple are returned we just fail
return null;
}
return $decoded[0];
}
/**
* Instruct implicit route binding to use
* our custom hashed parameter.
*
* This is long and crazy to avoid parameters
* collisions.
*
* @return string
*/
public function getRouteKeyName()
{
return 'hashidsRoutableHashParam';
}
/**
* Determine which attribute to encode
*
* @return string
*/
public function getRouteHashKeyName()
{
return $this->getKeyName();
}
/**
* Get beginning value
*
* @return string
*/
public function getRouteHashKey()
{
return $this->getAttribute($this->getRouteHashKeyName());
}
/**
* Encode real parameter to url value for bindings
*
* @return string
*/
public function getHashidsRoutableHashParamAttribute()
{
return $this->encodeParameter($this->getRouteHashKey());
}
/**
* Transform a checking by hashed key to real query
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function where()
{
$params = func_get_args();
if ($params[0] == $this->getRouteKeyName()) {
if (is_null($decoded = $this->decodeParameter($params[1]))) {
// Decoding failed so we return a query with no results
return parent::whereRaw('0 = 1');
}
return parent::where($this->getRouteHashKeyName(), $decoded);
}
return parent::where(...$params);
}
}

HashIds Routable Models

Turns a model binding from /profile/1 to /profile/XDBoYzYE just by using a trait.

Installation

This trait is based on laravel-hashids, require it first using composer. Then just copy paste the trait below somewhere in your laravel project.

Usage

Basic Usage

To use the encoded version of model's id just use the trait like:

<?php
// ...
class User extends Authenticatable
{
    use HashidsRoutable;
    // ....
}

Custom Attribute

To use another attribute rather then id override the getRouteHashKeyName() method:

<?php
// ...
class User extends Authenticatable
{
    use HashidsRoutable;
    
    public function getRouteHashKeyName()
    {
        return 'another_attribute_name';
    }
    // ....
}

Custom connection

By default the trait will use laravel-hashids's default connection, to change it on a per-model base override the getHashidsConnection() method:

<?php
// ...
class User extends Authenticatable
{
    use HashidsRoutable;
    
    public function getHashidsConnection()
    {
        return 'alternative';
    }
    // ....
}

Without laravel-hashids

If you don't want to use laravel-hashids (Why shouldn't you?), change the getHashidsInstance() method to return a new instance of hashids.php:

<?php
// ...
trait HashidsRoutable
{
    protected function getHashidsInstance()
    {
        return new Hashids\Hashids('your-salt', 0, 'abcdefghijklmnopqrstuvwxyz');
    }
    
    // ...

Disclaminer

Overriding the where() method of the Model class might be consider dirty. If you don't like it, use Explicit Binding instead of this trait to build your urls.

@pmochine
Copy link

Thanks man! It really helps.

However, I have one little question. I get an error when I'm trying to use where statically.

So just for an example:

 return Comment::where('commentable_type', $data['commentable_type'])
		->where('commentable_id', \Hashids::decode($data['commentable_id'])[0])
		->where('id', $filter, $lastCommentId)
                ->latest()
		->paginate(10);

Would give me an error with:

"Non-static method App\Comment::where() should not be called statically"

@hakimihamdan88
Copy link

Thanks man! It really helps.

However, I have one little question. I get an error when I'm trying to use where statically.

So just for an example:

 return Comment::where('commentable_type', $data['commentable_type'])
		->where('commentable_id', \Hashids::decode($data['commentable_id'])[0])
		->where('id', $filter, $lastCommentId)
                ->latest()
		->paginate(10);

Would give me an error with:

"Non-static method App\Comment::where() should not be called statically"

any solution?

@ruaq
Copy link

ruaq commented Dec 11, 2018

@pmochine @hakimihamdan88 I found a solution!

Remove the public function where() from the trait.

Insert

Route::bind('id', function ($id) { return \Hashids::decode($id)[0] ?? $id; });

in app/Providers/RouteServiceProvider.php in the public function boot() section and everything is fine!

I've forked it under https://gist.github.com/abasjo/9fdca139659d60ad56c833495b19098a (quick & dirty).

Hope it helps!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment