5
votes

I'm trying to create tables that will have a primary key which is a UUID defined as binary(16) instead of the default auto-incrementing id field.

I've managed to create migrations using raw SQL statements though DB::statement like so:

DB::statement("CREATE TABLE `binary_primary_keys` (
                      `uuid` binary(16) NOT NULL DEFAULT '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0',
                      `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
                      `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
                      PRIMARY KEY (`uuid`)
                  ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;");

However, I have trouble getting the model working. I've followed the tutorial available here. I've defined my model like so:

class UuidModel extends Model
{
    public $incrementing = false;
    public $primaryKey = 'uuid';

    /**
     * The "booting" method of the model.
     *
     * @return void
     */
    protected static function boot()
    {
        parent::boot();

        /**
         * Attach to the 'creating' Model Event to provide a UUID
         * for the `id` field (provided by $model->getKeyName())
         */
        static::creating(function ($model) {
            $model->{$model->getKeyName()} = (string)$model->generateNewId();
            echo($model->{$model->getKeyName()});
        });
    }

    /**
     * Get a new version 4 (random) UUID.
     */
    public function generateNewId()
    {
        return Uuid::generate();
    }
}

where Uuid is an alias to Webpatser\Uuid.

One problem, I'm having is I cannot derive UuidModel from Eloquent as explained in the tutorial. In fact I don't see an Eloquent class. I'm deriving from Model instead. I am guessing the tutorial was written in Laravel 4.

I would appreciate help in implementing tables with UUIDs as primary keys in Laravel 5.

EDIT 1: So, if I define my class like so:

use Illuminate\Database\Eloquent
class UuidModel extends Eloquent { ... }

I get the following error:

PHP Fatal error:  Class 'Illuminate\Database\Eloquent' not found in /home/vagrant/transactly/app/UuidModel.php on line 8

If I remove the use Illuminate\Database\Eloquent line, I get the following error:

PHP Fatal error:  Class 'App\Eloquent' not found in /home/vagrant/transactly/app/UuidModel.php on line 8

Edit 2: I have discovered that the static::creating event is never called for when instances of UuidModel are created.

I tried setting up the creating event listener in AppServiceProvider but that's not being called as well. Interestingly, the creating event is not called for a regular Laravel generated model User either.

class AppServiceProvider extends ServiceProvider
{
/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
    /**
     * Attach to the 'creating' Model Event to provide a UUID
     * for the `id` field (provided by $model->getKeyName())
     */
    echo "Booting...\n";
    UuidModel::creating(function ($model) {
        echo "Creating Uuid Model...\n";
        $model->{$model->getKeyName()} = (string)$model->generateNewId();
    });

    User::creating(function($user){
        echo "Creating User Model...";
        $user->name = 'Forced Name in boot()';
    });
}
public function register(){}

}
4
Did you try id with char(36)? - Leonardo Jorge
No I didn't. I'd imagine that char(36) would be less efficient. - Code Poet
I really don't know, but i think that compare binaries does not had the same precision in comparison and if you use, you should convert to binary, right? This article can be helpful kccoder.com/mysql/uuid-vs-int-insert-performance - Leonardo Jorge
I think, at the bit level, a binary(16) is just a number, only four times larger than a regular int(4). Integer comparisons will always be faster than char(36) comparisons. The article you point out to proves char comparisons are slow. If I were to hazard a guess, I'd say the author is seeing the slowdown because he's using a char(36) column as pk instead of binary(16). - Code Poet
So you're saying that you get an error that the Eloquent class is not found? If so, please post the exact error you're getting. - Bogdan

4 Answers

4
votes

How about this idea for storing a 36chr UUID as Binary(16) :

IMO there is an advantage in not having Laravel generating the UUID. Namely, if new records (some day in the future) get inserted into the database from outside the application the UUID field is properly populated.

My suggestion: Create a UUID default value trigger using migrations

(this trigger makes the DataBase server do the work to generate the UUID each time a new customer is inserted)

<?php namespace MegaBank\HighInterestLoans\Updates;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Database\Migrations\Migration;

class MigrationTriggerForCustomers extends Migration
{
public function up()
    {
    
        DB::unprepared('CREATE TRIGGER before_insert_customers
                      BEFORE INSERT ON    
                    `megabank_highinterestloans_customers` 
                      FOR EACH ROW
                      SET new.uuid = UNHEX(REPLACE(UUID(), "-","");');
    }

    public function down()
    {
        DB::unprepared('DROP TRIGGER `before_insert_customers`');
    }
}

Finally, if you want to get a human-readable version of your UUID just do the following:

SELECT HEX(UUID) FROM customers;

Anyway, hope this helps someone :-)

3
votes

So, I got the thing working like a charm (not tested unit testing):

  1. class UuidModel extends Eloquent is an older (Laravel 4) construct. We use class UuidModel extends Model in Laravel 5
  2. The solution was to move the

    UuidModel::creating(function ($model) {
        echo "Creating Uuid Model...\n";
        $model->{$model->getKeyName()} = (string)$model->generateNewId();
    });
    

    from AppServiceProvider::boot() to EventServiceProvider::boot(). No other changes were required. Everything worked as expected.

I still don't know why (2) works in EventServiceProvider and not in AppServiceProvider as explained in the official docs. But judging from the name, that's perhaps the way it was meant to be.

0
votes

This is a quick solution without using events.

UUidModel.php

<?php        namespace App;

    use \Illuminate\Database\Eloquent\Model;

    use \Illuminate\Database\Eloquent\Builder;

    class UuidModel extends Model

    {

    /**

    * Insert the given attributes and set the ID on the model.

    *

    * @param  \Illuminate\Database\Eloquent\Builder  $query

    * @param  array  $attributes

    * @return void

    */

        protected function insertAndSetId(Builder $query, $attributes)

        {
            $keyName = $this->getKeyName();
            $id = $attributes[$keyName] = $this->generateNewId();
            $query->insert($attributes);
            $this->setAttribute($keyName, $id);
        }

    /**
    * Get a new version 4 (random) UUID.
    */
        public function generateNewId()
        {
            return 'uuid!!' ;// just for test
        }
    }
?>

Model Example Car.php

<?php namespace App;

    class Car extends UuidModel {
    }
-3
votes

also try use this package will automatically generate and assign UUID field in your model, also can show and update by UUIDs key.

https://github.com/EmadAdly/laravel-uuid