Search code examples
phplaravellaravel-5laravel-5.3

L5 How to use trait that hashes id but keep pivot functionality


I added hashes to my ID's using a trait. However by doing that now I can no longer use attach() or relationships.

For example this relationship does not work in my view anymore:

@foreach ($invoice->items as $item)
   {{ $item->item }}
@endforeach

Here is the trait that hashes the id for me

<?php
namespace App\Traits;

use Hashids\Hashids;
use Illuminate\Database\Eloquent\Builder;

trait HashedId
{


    /**
     * Get the user's id as hashids.
     *
     * @param  $value
     * @return string
     */
    public function getIdAttribute($value)
    {
        $hashids = new \Hashids\Hashids(env('APP_KEY'),10);
        return $hashids->encode($value);
    }

    public function scopeHashId(Builder $query, $id)
    {
        $hashIds = new Hashids(env('APP_KEY'), 10);
        $id = $hashIds->decode($id)[0];

        return $query->where('id', $id);
    }
}

Invoice Model:

<?php

namespace App;

use App\Traits\HashedId;
use Illuminate\Database\Eloquent\Model;
use HipsterJazzbo\Landlord\BelongsToTenants;

class Invoice extends Model
{
    use BelongsToTenants;
    use HashedId;
    //
    protected $fillable = [
        'client_id',
        'invoice_number',
        'purchase_order',
        'invoice_note',
        'invoice_status',
        'invoice_total',
        'invoice_type',
        'sub_total',
        'balance_due',
        'due_date',
        'invoice_type',
        'user_id',
    ];

    protected $hidden = [
        'user_id'
    ];

    public function items()
    {
        return $this->belongsToMany('App\LineItem', 'invoice_items', 'invoice_id', 'item_id');
    }

    public function client()
    {
        return $this->belongsTo('App\Client');
    }

}

I have tried doing this from a controller but it feels more like a hack than the right way to do it and I still lose the ability to use things like $invoice->attach($lineItem) or $invoice->items

//Currently I have to unhash the ids in order to save them as a pivot
$hashIds = new \Hashids\Hashids(env('APP_KEY'), 10);
$invoiceId = $hashIds->decode($request->invoice_id)[0];
$lineItemId = $hashIds->decode($request->item_id)[0];

//Should have been able to use $invoice->attach($lineItemId)
DB::table('invoice_items')->insert(
  ['invoice_id' => $invoiceId, 'item_id' => $lineItemId]
);

How can I continue to use $invoice->attach($lineItem) or $invoice->items from controllers while still using the trait that hashes my ids?


Solution

  • I've re-written the trait as follows (this assumes you're using PHP 5.6 or above):

    <?php 
    
    namespace App\Traits;
    
    use Hashids\Hashids;
    use Illuminate\Database\Eloquent\Builder;
    
    trait HashedId
    {
        /**
         * Get model ID attribute encoded to hash ID.
         *
         * @return string
         */
        public function getHashIdAttribute()
        {
            $hashIds = new Hashids(env('APP_KEY'), 10);
            return $hashIds->encode($this->getKey());
        }
    
        /**
         * Restrict query scope to find model by encoded hash ID.
         * 
         * @param  \Illuminate\Database\Eloquent\Builder  $query
         * @param  integer  $id
         * @return \Illuminate\Database\Eloquent\Builder
         */
        public function scopeHashId(Builder $query, $id)
        {
            $hashIds = new Hashids(env('APP_KEY'), 10);
            $id = $hashIds->decode($id)[0];
    
            return $query->where('id', $id);
        }
    
        /**
         * Restrict query scope to find models by encoded hash IDs.
         * 
         * @param  \Illuminate\Database\Eloquent\Builder  $query
         * @param  array  $ids
         * @return \Illuminate\Database\Eloquent\Builder
         */
        public function scopeHashIds(Builder $query, ...$ids)
        {
            $hashIds = new Hashids(env('APP_KEY'), 10);
    
            $ids = array_map(function ($id) use ($hashIds) {
                return $hashIds->decode($id)[0];
            }, $ids);
    
            return $query->whereIn('id', $ids);
        }
    }
    

    You may notice that I've renamed the accessor, getIdAttribute() to getHashIdAttribute(). You can therefore now get the hash ID of a model instance by calling $model->hash_id instead of $model->id.

    This is where I think your problem was, because Laravel was expecting an integer key to be returned by $model->id, whereas it would have been getting the hash ID instead.

    If after implementing the changes above you're still getting an error, can you show what the specific error is?