Using a UUID as primary key with Laravel 5 - php

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(){}
}

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 :-)

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 5
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.

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 {
}

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

Related

How can I change database name conventions in Laravel?

I want to change the default database naming conventions in my Laravel app. By default, Laravel uses snake case for database table and column names. But I want to use Pascal Case for table names and i want to use camel Case for fields.
So a table name of Users instead of users, and field names createdAt, updatedAt, and deletedAt instead of created_at, updated_at, and deleted_at.
I know I can change these on a per-model basis using the $table property but I'd like to change the default without having to modify each model.
Are there any settings like Symfony's NamingStrategy in Laravel?
If you look at the code for Illuminate\Database\Eloquent\Model::getTable() it's pretty straightforward:
public function getTable()
{
return $this->table ?? Str::snake(Str::pluralStudly(class_basename($this)));
}
Same for Illuminate\Database\Eloquent\Concerns\HasTimestamps::getCreatedAtColumn():
public function getCreatedAtColumn()
{
return static::CREATED_AT;
}
So create your own class that extends Model and override that behaviour:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model as BaseModel;
use Illuminate\Support\Str;
class Model extends BaseModel
{
const CREATED_AT = 'createdAt';
const UPDATED_AT = 'updatedAt';
const DELETED_AT = 'deletedAt';
public function getTable()
{
return $this->table ?? Str::pluralStudly(class_basename($this));
}
}
Now, just have your models extend this class.
You can use stubs for that. Also if you named your table in Pascal Case just use $table property in model which need to be sync with that table.
You can create a new model that you extend your models with.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class ModelWithPascalCase extends Model
{
const DELETED_AT = 'deletedAt';
const CREATED_AT = 'createdAt';
const UPDATED_AT = 'updatedAt';
public function getTable()
{
return $this->table ?? Str::pluralStudly(class_basename($this));
}
}
If you want to make Laravel generate your models extending this, you can do it by editing stubs.
Run
artisan stub:publish
then edit stubs/model.stub by replacing Model with your ModelWithPascalCase.
After that, when you run
artisan make:model User
you get your User model extended by ModelWithPascalCase.

Can't set value for Non-relational column in pivot table with Laravel factory

hey everybody is it technically possible to set data in an extra column in the pivot table?
my database is like
products
sellers
id
id
title
name
brand_id
and...
prodduct_sellrs
id
product_id
seller_id
price
and I want to Seed my database with factory Faker data and here is my code in my seeder (I've already made the ProductFactory and.... )
Product::factory()
->foruser()
->hasCategories()
->hasTags()
->hassellers()
->forBrand()
->forAttributeSet()
->create();
but when I run it I get this error
General error: 1364 Field 'price' doesn't have a default value
which is truly right but anybody can help how I can define value for price in the pivot table?
You can use factory callbacks
Factory callbacks - Laravel
After create your Product, you can create data for your relational tabble
namespace Database\Factories;
use App\Models\Product;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class ProductFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Product::class;
/**
* Configure the model factory.
*
* #return $this
*/
public function configure()
{
return $this->afterCreating(function (Product $product) {
$product->sellers()->save(factory(App\ProductSeller::class)->make());
});
}
// ...
}
I realized my answer If anyone wanted to use
Product::factory()
->foruser()
->hasCategories()
->hasAttached(
Seller::factory()->count(10),
['price'=>423432432]
)
and it solved, here it is

Eloquent model is returning as builder

I'm relearning Laravel with laravel 7 and have hit an issue where I'm unable to query a record in my database table. So instead of a call like $test = Test::find_by_id_and_name(1, 'test 1'); (and also $test = Test::where('id', 1); returning a class of Illuninate\Database\Eloquent\Model it returns a class of Illuminate\Database\Eloquent\Builder.
I have created a Migration for a table called Tests and seeded it with a few rows of test data. The Test Model in App is as follows
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Test extends Model
{
protected $guarded = [];
use SoftDeletes;
}
the Migration is:
se Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTestsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('tests', function (Blueprint $table) {
$table->id();
$table->string( 'name' );
$table->string( 'url', 255 );
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('tests');
}
}
So anyone any idea why I'm not getting the Model I need so i can do for instance a dd($test); and see the values stored in the database for the row with the id of 1? or even do an echo($test->name); and see the name of this item?
thanks
* ADDITIONAL *
Should of pointed out my initial code had Test::find_by_id_and_name(1, 'test 1'); but this didn't work and throw an exception about finding the class. I modified if with where and above was a typo as it was where( 'id', 1 ); (I've corrected the code using my initial find_by code). Adding a get() or any other thing now returns null. I have verified that the database contains the table tests and that an item with the id and name of 'test 1' exists
* RESULT *
The underlying issue in the end was the data, the url had https::// in it (additional colon) so indeed it would return null. Thanks guys helped me find the reason.
Misunderstanding of query builder vs models in Laravel. Check doc for reference.
Calling query builder method statically on a model returns a builder.
User::where('id', 1); // returns builder
To resolve a query builder you can either use get() or first().
User::where('id', 1)->get(); // Returns a collection of users with 1 element.
User::where('id', 1)->first(); // Returns one user.
You can also fetch the user out of the collections this is not recommended as you might as well just call first().
User::where('id', 1)->get()->first(); // Returns collection fetches first element that is an user.
Laravel has static methods for finding models by id.
User::find(1); // returns user or null
User::findOrFail(1); // returns user or exception
Try to use the following
$test = Test::find(1);
Then you will get the record,

Laravel polymorphic with custom foreign key

I have problem with custom shipmentable_type. My structure looks like this:
transfers:
id - integer
name - string
shipment:
id - integer
name - string
shipmentale:
shipment_id - integer
shipmentable_id - integer
shipmentabl_type - enum ( transfer, order, complaint, internet )
Now I have in my Transfer model realtion like this:
public function shipments()
{
return $this->morphToMany(Shipment::class, 'shipmentable');
}
The problem is, that to table shipmentable, to column shipmentable_type is going sth like this now: App/Models/Transfer, but I would like to force to be there 'transfer' in this case. Is it possible?
From the docs
By default, Laravel will use the fully qualified class name to store the type of the related model. However, you may wish to decouple your database from your application's internal structure. In that case, you may define a relationship "morph map" to instruct Eloquent to use a custom name for each model instead of the class name:
use Illuminate\Database\Eloquent\Relations\Relation;
Relation::morphMap([
'transfer' => 'App/Models/Transfer'
]);
You may register the morphMap in the boot function of your AppServiceProvider or create a separate service provider if you wish.
To set a value other than the model's fully qualified name in shipmentable_type, there is the Relation::morphMap() function.
Relation::morphMap([
'transfer' => 'App/Models/Transfer'
]);
This should be set in the AppServiceProvider or similar.
I've had this issue but unfortunately I had multiple models pointing at transfer with multiple locations. You can resolve them dynamically on your model:
class Transfers extends Model
{
/**
* Declare the class to get connect polymorphic relationships.
*
* #var string|null
*/
protected $morphClass = null;
/**
* Get the associated morph class.
*
* #return string|null
*/
public function getMorphClass()
{
return $this->morphClass ?: static::class;
}
public function shipments()
{
$this->morphClass = 'transfer';
return $this->morphToMany(Shipment::class, 'shipmentable');
}
// ...
}

how does laravel finds the connection between models and its table in database

in my model i have
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Page extends Model
{
//
}
in my controller i can say Page:all() and get all the rows from pages table
but i dont see any connection between Page model and pages table in the database
does it just guess table name based on the model name (lower case with extra s at the end ) or it's mentioned somewhere else ?
As you can see in the docs, this is the magic of Laravel :-)
https://laravel.com/docs/5.2/eloquent#defining-models (see Table Names)
If you want, you can set another name manually by user the following
protected $table = 'my_table_name';
And to go a bit further, this is how Laravel gets the table name in the base Model you can found at /vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
/**
* Get the table associated with the model.
*
* #return string
*/
public function getTable()
{
if (isset($this->table)) {
return $this->table;
}
return str_replace('\\', '', Str::snake(Str::plural(class_basename($this))));
}
You can specify the table by putting the below code into your model
protected $table = 'your table name';
Otherwise, it takes a table name as a plural form of the model.
For example, if your model name Product then by default it connects the table named 'products'.
So if your table name is not different then no need to configure it. It's will connect automatically
By default, it takes the "snake case" of the class name used in the model. Also, add a final "s" to make the plural.
You can also define a custom name by adding the variable
protected $table = 'my_table_name';
in the model class.
For instance:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'my_flights';
}

Categories