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(){}
}
You only need some steps to change your Model so use UUID as default. Create Uuid Trait, for example I place the file in App\Traits with name Uuid. php. In this example, we use id column as our primary key, you can use another column as your want.
The following are the advantages of using UUID for a primary key: UUID values in MySQL are unique across tables, databases, and servers. It allows us to merge rows from distributed/different databases across servers. UUID values do not provide information about our data, which means it is hard to guess.
UUIDs are created by your application, so you don't have to wait for the database server to create new users. Since you create your UUIDs independent of the database, you can split your data between multiple database servers. UUIDs are secure, as they're randomly generated and unique, so they can't be guessed.
UUID always occupies 16 bytes. For Auto Increment Integer, when stored as Long format, it occupies 8 bytes. If the table itself has only a few columns, the extra primary key space overhead will become more significant.
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.
(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 :-)
So, I got the thing working like a charm (not tested unit testing):
class UuidModel extends Eloquent
is an older (Laravel 4) construct. We use class UuidModel extends Model
in Laravel 5The 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With