lararvel uuid as primary key - laravel

I'm trying to set an uuid as primary key in a Laravel Model. I've done it setting a boot method in my model as stablished here so I don't have to manually create it everytime I want to create and save the model. I have a controller that just creates the model and saves it in database.
It is saved correctly in database but when controller returns the value of the id is always returned with 0. How can I make it to actually return the value that it is creating in database?
Model
class UserPersona extends Model
{
protected $guarded = [];
protected $casts = [
'id' => 'string'
];
/**
* Setup model event hooks
*/
public static function boot()
{
parent::boot();
self::creating(function ($model) {
$uuid = Uuid::uuid4();
$model->id = $uuid->toString();
});
}
}
Controller
class UserPersonaController extends Controller
{
public function new(Request $request)
{
return UserPersona::create();
}
}

You need to change the keyType to string and incrementing to false. Since it's not incrementing.
public $incrementing = false;
protected $keyType = 'string';
Additionally I have an trait which I simply add to those models which have UUID keys. Which is pretty flexible. This comes originally from https://garrettstjohn.com/articles/using-uuid-laravel-eloquent-orm/ and I added some small adjustments to it for issues which I have discovered while using it intensively.
use Illuminate\Database\Eloquent\Model;
use Ramsey\Uuid\Uuid;
/**
* Class Uuid.
* Manages the usage of creating UUID values for primary keys. Drop into your models as
* per normal to use this functionality. Works right out of the box.
* Taken from: http://garrettstjohn.com/entry/using-uuids-laravel-eloquent-orm/
*/
trait UuidForKey
{
/**
* The "booting" method of the model.
*/
public static function bootUuidForKey()
{
static::retrieved(function (Model $model) {
$model->incrementing = false; // this is used after instance is loaded from DB
});
static::creating(function (Model $model) {
$model->incrementing = false; // this is used for new instances
if (empty($model->{$model->getKeyName()})) { // if it's not empty, then we want to use a specific id
$model->{$model->getKeyName()} = (string)Uuid::uuid4();
}
});
}
public function initializeUuidForKey()
{
$this->keyType = 'string';
}
}
Hope this helps.

Accepted answer not worked for me on Laravel 9, but this way worked perfect, you can try it:
1- Create new Trait Class in project path app/Traits/IdAsUuidTrait.php (if you not found Traits folder create it, this is full code of this Class:
<?php
namespace App\Traits;
use Illuminate\Support\Str;
trait IdAsUuidTrait
{
public function initializeIdAsUuidTrait(): void
{
$this->keyType = 'string';
$this->id = Str::orderedUuid()->toString();
}
}
2- In any model you want to make id as UUID just call trait like this:
use App\Traits\IdAsUuidTrait;
class YourModelName extends Model
{
use IdAsUuidTrait;
...
That is it, now try to create, select, update any row in database by this model...

Related

Laravel8.x - Getting exception while accessing belongsTo relationship

I have a model class created in my laravel 8.x application with the code as given below
class City extends Model
{
use HasFactory;
protected $table = 'portal_cities';
public function belongsTo()
{
return $this->belongsTo(State::class); // also tried giving 'state_id' as second parameter
}
}
I have the portal_cities table as below
When i am trying to access the following code
$eventobj = App\Models\Event::find(1);
echo $eventobj->location->city->name;
It is giving the following error
Declaration of App\Models\City::belongsTo() should be compatible with Illuminate\Database\Eloquent\Model::belongsTo($related, $foreignKey = NULL, $ownerKey = NULL, $relation = NULL)
Can you please tell me what is causing the error and what can be done to rectify it?
change relation method name
for example:
public function state()
{
return $this->belongsTo(State::class); // also tried giving 'state_id' as second parameter
}
you can't create belongsTo() function as it is core function in model
so you need to change this
class City extends Model
{
use HasFactory;
protected $table = 'portal_cities';
public function state() // change this name
{
return $this->belongsTo(State::class);
}
}
you can check core function here
https://github.com/laravel/framework/blob/8.x/src/Illuminate/Database/Eloquent/Concerns/HasRelationships.php#L193

Laravel Mystery - Two Similar Item Types Producing 2 Different Query Strings in Same Use Case

Ok, this is weird... You ready?
I have an item type on my site, lets call it SomeItem
It can have tags associated with it via a one-to-many relationship.
The sorts of queries that Laravel builds when dealing with tags for SomeItem are like this, for instance in response to route api/someitem/10:
select `tags`.*, `someitem_tag`.`someitem_id` as `pivot_someitem_id`, `someitem_tag`.`tag_id` as `pivot_tag_id` from `tags` inner join `someitem_tag` on `tags`.`id` = `someitem_tag`.`tag_id` where `someitem_tag`.`someitem_id` in (10)
When I create a second Item with identical settings - let's call it AnotherItems - it treats the database query for extracting tags in a different manner, using a different syntax in the queries. Extremely weird.
(and yes, I have an s at the end of the model name...)
For instance, this route api/anotheritems/1
produces this error:
Base table or view not found: 1146 Table 'mysite.tag_anotheritems' doesn't exist (SQL: select `tags`.*, `tag_anotheritems`.`anotheritems_id` as `pivot_anotheritems_id`, `tag_anotheritems`.`tag_id` as `pivot_tag_id` from `tags` inner join `tag_anotheritems` on `tags`.`id` = `tag_anotheritems`.`tag_id` where `tag_anotheritems`.`anotheritems_id` in (1))
See what is happening? Of course I am getting this error - in the database this tag table for AnotherItems is created as anotheritems_tag. That is analogous to SomeItem.
How on earth can Laravel be using syntax someitem_tag for one item but tag_anotheritems for another item??? WTF?
First let me show you how SomeItem is set up.
Here is the database structure related to Tags:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateSomeItemTagTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('someitem_tag', function (Blueprint $table) {
$table->integer('tag_id')->unsigned();
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
$table->integer('someitem_id')->unsigned();
$table->foreign('someitem_id')->references('id')->on('someitems')->onDelete('cascade');
$table->primary(array('tag_id', 'someitem_id'));
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('someitem_tag');
}
}
There is a Tags model/class that has this:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tag extends Model
{
protected $fillable = ['name'];
protected $hidden = [];
public $timestamps = false;
public function someitems()
{
return $this->belongsToMany(SomeItem::class);
}
}
And here is some relevant lines for SomeItem model/class:
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use App\Presenters\Presentable;
use Illuminate\Notifications\Notifiable;
use Auth;
class Exercise extends Model
implements Presentable
{
use Traits\SerializesUniversalDate;
use Traits\Presents;
use Notifiable;
protected $presenter = 'App\Presenters\SomeItemPresenter';
protected $fillable = ['title', etc];
protected $hidden = [];
public function parentitem()
{
return $this->belongsTo(ParentItem::class);
}
public function tags()
{
return $this->belongsToMany(Tag::class);
}
/**
* Update lesson tag array.
*
* #param array \App\Tag $tags
* #return void
*/
public function updateTags($tagsArray)
{
foreach ($tagsArray as &$value)
{
$tag = Tag::where('name', $value['name'])->first();
if (is_null($tag))
{
$tag = new Tag([
'name' => $value['name']
]);
$tag->save();
}
if (!$this->tags->contains($tag->id))
{
$this->tags()->attach($tag->id);
}
}
foreach($this->tags as &$existingTag)
{
if (!self::arrayContains($tagsArray, 'name', $existingTag->name))
{
$this->tags()->detach($existingTag->id);
}
}
$this->load('tags');
}
private static function arrayContains($array, $key, $value)
{
foreach ($array as $item)
{
if($item[$key] == $value) return true;
}
return false;
}
}
And here is some relevant code for SomeItem API controller:
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Input;
class SomeItemController extends Controller
{
public function index(Request $request)
{
$query = \App\SomeItem::query();
return $query->get()->load('parentitem')->load('tags');
}
//show item for editing
public function show($id)
{
$someitem = \App\SomeItem::find($id);
$someitem->load('parentitem')->load('tags');
$someitem->attachKindToFiles();
return $someitem;
}
//store new entry to db
public function store()
{
$someitem = \App\SomeItem::create(Input::all());
isset(Input::all()['tags']) ? $someitem->updateTags(Input::all()['tags']) : '';
return $someitem;
}
//update/save
public function update($id)
{
$someitem = \App\SomeItem::find($id);
$someitem->update(Input::all());
$someitem->updateTags(Input::all()['tags']);
$someitem->load('tags');
return $someitem;
}
There is also a SomeItem presenter and composer but they don't do anything with tags.
With AnotherItems, I literally I duplicated everything from SomeItem and just changed names as needed.
So in the Tag model there is
public function anotheritems()
{
return $this->belongsToMany(AnotherItems::class);
}
In AnotherItems model there is this, for instance
public function tags()
{
return $this->belongsToMany(Tag::class);
}
In the AnotherItems API controller there is this, for instance (which is for route api/anotheritems/1):
public function index(Request $request)
{
$query = \App\AnotherItems::query();
if ($request->has('id')) {
$query->where('id', $request['id']);
}
return $query->get()->load('parentitem')->load('tags');
}
So, this is a total mystery. I have been trying to figure this out for 2 days now. And I continue asking myself
How on earth can Laravel be using syntax someitem_tag for one item but tag_anotheritems for another item???
I upgraded from laravel 5.2 to 5.3 and it is after the upgrade that I added this AnotherItems. But I can't figure out how that could possibly alter things in terms of these database queries.
I have tried a ton of artisan commands for clearing everything imaginable, but somewhere in the framework it wants to handle SomeItem and AnotherItems differently when building these join queries to extract/save tags.
Thoughts?
thanks,
Brian
Decided to step through code in debugger. Seems things are breaking down in Str.php in various snake related function, and I also noticed a snakeCache call, whatever the heck that is. Not sure why such a strange methodology to determine table names... Also in these functions there is some pluralizing related checks, so maybe this is related to me using an s at the end of my item name. Pretty messed up stuff if an s at the end of a model name can cause two different logic branches...

Laravel - how to makeVisible an attribute in a Laravel relation?

I use in my model code to get a relation
class User extends Authenticatable
{
// ...
public function extensions()
{
return $this->belongsToMany(Extension::class, 'v_extension_users', 'user_uuid', 'extension_uuid');
}
// ...
}
The Extension has field password hidden.
class Extension extends Model
{
// ...
protected $hidden = [
'password',
];
// ...
}
Under some circumstances I want to makeVisible the password field.
How can I achieve this?
->makeVisible([...]) should work:
$model = \Model::first();
$model->makeVisible(['password']);
$models = \Model::get();
$models = $models->each(function ($i, $k) {
$i->makeVisible(['password']);
});
// belongs to many / has many
$related = $parent->relation->each(function ($i, $k) {
$i->makeVisible(['password']);
});
// belongs to many / has many - with loading
$related = $parent->relation()->get()->each(function ($i, $k) {
$i->makeVisible(['password']);
});
Well, I got the idea from https://stackoverflow.com/a/38297876/518704
Since my relation model Extension::class is called by name in my code return $this->belongsToMany(Extension::class,... I cannot even pass parameter to it's constructor.
So to pass something to the constructor I may use static class variables.
So in my Extension model I add static variables and run makeVisible method.
Later I destruct the variables to be sure next calls and instances use default model settings.
I moved this to a trait, but here I show at my model example.
class Extension extends Model
{
public static $staticMakeVisible;
public function __construct($attributes = array())
{
parent::__construct($attributes);
if (isset(self::$staticMakeVisible)){
$this->makeVisible(self::$staticMakeVisible);
}
}
.....
public function __destruct()
{
self::$staticMakeVisible = null;
}
}
And in my relation I use something like this
class User extends Authenticatable
{
...
public function extensions()
{
$class = Extension::class;
$class::$staticMakeVisible = ['password'];
return $this->belongsToMany(Extension::class, 'v_extension_users', 'user_uuid', 'extension_uuid');
}
...
}
The highest voted answer didn't seem to work for me (the relations attribute seems to be a protected array now so can't be used as a collection in #DevK's answer), I instead used:
$parent->setRelation('child', $parent->child->first()->setVisible(['id']));

Laravel Mutators with Constructor?

Ill have a problem because my mutators never get called when ill use an constructor:
Like this:
function __construct() {
$this->attributes['guid'] = Uuid::generate(4)->string;
}
public function setDateAttribute($date) {
dd($date); // Never gets called
}
Ill already found out, that the mutators would ne be called when ill use an constructor, so i should use:
public function __construct(array $attributes = array()){
parent::__construct($attributes);
$this->attributes['guid'] = Uuid::generate(4)->string;
}
public function setDateAttribute($date) {
dd($date); // now its getting called
}
But so ill get the following error:
array_key_exists() expects parameter 2 to be array, null given
But i dont know where? Can anyone help me out how to create a default value (like a UUID) for a specific column, and use mutators in the same class?
Edit: Thanks Martin Bean for your help, but i am now getting the following error:
Cannot declare class App\Uuid because the name is already in use
I have tried:
Creating a File called "Uuid.php" in /app/ -> /app/Uuid.php
With this content:
<?php namespace App;
use Webpatser\Uuid\Uuid;
trait Uuid
{
public static function bootUuid()
{
static::creating(function ($model) {
$model->uuid = Uuid::generate(4)->string();
});
}
}
Changed my Model to:
<?php namespace App;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
class Task extends Model {
use \App\Uuid;
Thank you very much!
Edit 2:
Ill tried it this way:
class Task extends Model {
protected $table = 'tasks';
protected $fillable = ['..... 'date', 'guid'];
public function setGuidAttribute($first=false){
if($first) $this->attributes['guid'] = Uuid::generate(4)->string;
}
TaskController:
public function store() {
$input = Request::all();
$input['guid'] = true;
Task::create($input);
return redirect('/');
}
Works fine, but when ill use:
public function setDateAttribute(){
$this->attributes['date'] = date('Y-m-d', $date);
}
In Task.php ill get:
Undefined variable: date
EDITED:
based on your comment:
i would like to set a field on first insert
use Uuid; //please reference the correct namespace to Uuid
class User extends Model{
protected $fillable = [
'first_name',
'email',
'guid' //add guid to list of your fillables
]
public function setGuidAttribute($first=false){
if($first) $this->attributes['guid'] = Uuid::generate(4)->string;
}
}
Later:
$user = User::create([
'guid' => true, //setAttribute will handle this
'first_name' => 'Digitlimit',
'email" => my#email.com
]);
dd($user->guid);
NB: Remove the __construct() method from your model
Mutators are called when you try and set a property on the model—they’re invoked via the __get magic method. If you manually assign a property in a method or constructor, then no mutators will ever be called.
Regardless, you should not be creating constructors on Eloquent model classes. This could interfere with how Eloquent models are “booted”.
If you need to set an UUID on a model then I’d suggest using a trait that has its own boot method:
namespace App;
trait Uuid
{
public static function bootUuid()
{
static::creating(function ($model) {
$model->uuid = \Vendor\Uuid::generate(4)->string();
});
}
}
You apply the trait to your model…
class SomeModel extends Model
{
use \App\Uuid;
}
…and now each time a model is created, a UUID will be generated and stored in the database with your model.

Laravel4 - Saving a model with multiple relationships/foreign keys

I've tried to understand a process of saving a model with multiple relationships but I still can't figure out how to do it "kosher" way.
To begin with - I have an Event model that belongs to a category (Eventcat) and a Location:
// Event.php
class Event extends Eloquent {
protected $table = 'events';
public function location()
{
return $this->belongsTo('Location');
}
public function eventcat()
{
return $this->belongsTo('Eventcat');
}
public function users()
{
return $this->belongsToMany('User');
}
}
// Location.php
class Location extends Eloquent
{
protected $table = 'locations';
public function events()
{
return $this->hasMany('Event');
}
}
// Eventcat.php
class Eventcat extends Eloquent
{
protected $table = 'eventcats';
public function events()
{
return $this->hasMany('Event');
}
}
I've seeded the database with a few categories and locations and now I trying to get events saving work. I thought that the $event->eventcat()->associate( $eventcat ) would work but I got a Call to undefined method eventcat() error.
public function postCreateEvent() {
$event = new Event();
$eventcat = Eventcat::find( Input::get('event-create-eventcat[]') );
$location = Location::find( Input::get('event-create-location[]') );
$event->title = Input::get('event-create-title');
$event->description = Input::get('event-create-description');
$event->price = Input::get('event-create-price');
$event->start_date = Input::get('event-create-start_date');
$event->end_date = Input::get('event-create-end_date');
$event->eventcat()->associate( $eventcat );
$event->location()->associate( $location );
$event->save();
}
I've read the documentation, API and a few threads here but I still can't figure out the best way to deal with this.
Thanks for replies!
I would actually bet that you have a conflict in your class name. Laravel contains an Event class and I wonder if that isn't what's being called in your code. As a quick test, you could rename your class FooEvent and see if it works.
The best solution is probably namespacing your model (see http://chrishayes.ca/blog/code/laravel-4-methods-staying-organized for a quick intro) so that your model can still be called Event without conflicting with the builtin class.

Resources