Many-To-Many Relationship doesnt seem to work with uuid
Subwaytime opened this issue · 33 comments
Hey there,
just tried to use a many to many relationship where both models have a uuid and i cant seem to get it working!
class Auctionhouse extends Model {
use HasJsonRelationships;
protected $casts = [
'itemCollection' => 'json',
];
protected $attributes = [
'itemCollection' => '[]'
];
public function soldItems(): BelongsToJson {
return $this->belongsToJson(Item::class, 'itemCollection[]->id');
}
}
class Item extends Model {
use HasJsonRelationships;
public function soldInAuctionhouse(): HasManyJson {
return $this->hasManyJson(Auctionhouse::class, 'itemCollection[]->id');
}
}
Item Id is a uuid via a HasUuid
Trait that sets incrementing
to false, keyType
to 'string' and primaryKey to 'id';
Idea is to add items to the auctionhouse via sync
->
$ac->soldItems()->sync(['077df06d-1c38-4fd6-b141-3544c6ddbc27' => ['price' => '200g']])->save());
Migrations are just using $table->json('itemCollection');
on the Auctionhouse Migration.
Database data looks like this
[{"id":"077df06d-1c38-4fd6-b141-3544c6ddbc27","price":"200g"}]
If i remove the json cast i cant seem to load the soldItems via $ac->soldItems
as this either returns an empty Eloquent Collection
Illuminate\Database\Eloquent\Collection {#560 ▼
#items: []
#escapeWhenCastingToString: false
}
if the cast for the itemCollection is json/array it will throw this error
array_key_exists(): Argument #2 ($array) must be of type array, null given
which is similiar to issue
Hopefully i provided all necessary informations! Let me know if something is missing!
Greetings
Hi @Subwaytime,
Thanks, I can reproduce this. The error occurs when the $attributes
property has a default value. I'm working on a fix.
What Laravel version are you using?
Using Laravel 9.13!
Is there a workaround for the time? Because the issue still exists even if i remove $attributes
! - This also occurs when setting a simple empty array []
into the json column..
Because the issue still exists even if i remove $attributes!
How are querying the soldItems
relationship in this case?
This also occurs when setting a simple empty array [] into the json column..
How are you setting that? What query are you then executing?
Right now i only tried
$ac->soldItems
and with/load
so nothing special - these didnt work
Added the array just via the mysql itself, so nothing from Laravel
I released a new version that fixes the issue for me. I can't reproduce it without default $attributes
so we'll have to see if it works for you.
Unfortunately it still does not work!
I am still getting the array_key_exists
error, with the same setup as above, just updated to 1.6.2
What query exactly are you executing? What's the whole stacktrace? Please also share the whole files of both models.
$character = Character::where('id', 'bc8207fe-a640-48d4-b576-8e9f5e1d01f6')->first();
$ac = Auctionhouse::whereBelongsTo($character)->first();
$ac->soldItems
I tried this and i tried adding protected $with = ['soldItems'];
into the auctionhouse model and on the query. Both dont work!
Both models look like above on difference is there is a fillable for auctionhouse with character_id
and itemCollection
.
The Uuid Trait looks like this, but i also tried different methods now for this, to get a work around going for this library..
<?php
declare(strict_types=1);
namespace App\Traits;
use Illuminate\Support\Str;
trait HasUuid {
/**
* Indicates if the IDs are auto-incrementing.
*
* @return bool
*/
public function getIncrementing() {
return false;
}
/**
* Returns Model Key.
*
* @return string
*/
public function getKeyName() {
return 'id';
}
/**
* Create Models with UUID.
*/
protected static function booted(): void {
static::creating(function ($model): void {
$model->keyType = 'string';
$id = (string) Str::uuid();
$model->{$model->getKeyName()} = $id;
});
}
}
edit: ive only got the library to work if i use only a string on the relation ship like itemCollection
if i use itemCollection[]->id
(or something else there like item_id etc) it doesnt work.
edit: stacktrace share via flare https://flareapp.io/share/q5YElxj5#F46
Any update on this? Keen to help if its needed!
What's the result of dd($ac->getAttributes());
?
array:5 [▼
"id" => "470f0a4f-cbaa-46e7-b056-da56079033ed"
"character_id" => "bc8207fe-a640-48d4-b576-8e9f5e1d01f6"
"itemCollection" => "[{"item_id": "077df06d-1c38-4fd6-b141-3544c6ddbc27"}]"
"created_at" => "2022-07-21 17:28:35"
"updated_at" => "2022-07-21 17:28:35"
]
this is the output
"itemCollection" => "[{"item_id": "077df06d-1c38-4fd6-b141-3544c6ddbc27"}]"
Are the foreign keys inside the JSON objects named item_id
or id
? In your initial post, it's id
:
return $this->belongsToJson(Item::class, 'itemCollection[]->id');
[{"id":"077df06d-1c38-4fd6-b141-3544c6ddbc27","price":"200g"}]
Oh sorry ive changed the model while testing out what the issue could be, the foreign keys are now item_id
, which still runs into the same issue as if its just using id
. Both fail for me.
Basically it doesnt seem to matter what foreign keys i have defined, only difference is the getAttribute output, the error mentioned above stays the same
I just can't reproduce this. What do you get when you put this in line 201
of vendor/staudenmeir/eloquent-json-relations/src/Relations/BelongsToJson.php
?
dd($model->getAttributes(), $parent->getAttributes(), $key, $this->ownerKey);
My Error still is array_key_exists(): Argument #2 ($array) must be of type array, null given when i call $ac->soldItems
With the dd()
call in line 201
or without it?
without it, with the dd call i endup with the image posted above
Please remove the dd()
call and put this in line 206
instead:
if (!$record) dd($model->getAttributes(), $parent->getAttributes(), $key, $this->ownerKey);
same result as the picture from above
hopefully thats correct -->
protected function pivotAttributes(Model $model, Model $parent)
{
$key = str_replace('->', '.', $this->key);
$record = (new BaseCollection($parent->{$this->path}))
->filter(function ($value) use ($key, $model) {
return Arr::get($value, $key) == $model->{$this->ownerKey};
})->first();
if (!$record) dd($model->getAttributes(), $parent->getAttributes(), $key, $this->ownerKey);
return Arr::except($record, $key);
}
class Auctionhouse extends Model {
class Item extends Model {
Is Model
Illuminate\Database\Eloquent\Model
or do you have a custom base model?
You are using version v1.6.2
of the package, right?
No both are from the default Laravel Eloquent Model - use Illuminate\Database\Eloquent\Model
Yes i am using 1.6.2
Can you reproduce it on a fresh Laravel installation? I don't have anything left to try, something must be different in your environment.
Will recreate a repo and post its link here!
https://github.com/Subwaytime/json-relation-repro
Hopefully i didnt miss anything - everything lives inside the web routes where i also provided an easy setup with uuids
under traits there is the usual uuid trait i use
note: i also discovered that if i use $item->id
on the sync method rather then an actual string array then it works and there is no error! So it might be that the string array just isnt working
Thanks. I can't access the repo, is it private maybe?
Ups sorry, forgot that! Its public now
under traits there is the usual uuid trait i use
Does the Item
model use the HasUuid
trait in the original application where the error first occurred? The trait is not used in json-relation-repro
and that's what causes the issue: When you don't override the $keyType
, the UUID is cast to an integer:
$item->id // 77 instead of 077df06d-1c38-4fd6-b141-3544c6ddbc27
It works when you use the trait:
class Item extends Model
{
use HasFactory, HasJsonRelationships, HasUuid;
Interesting, yeah using the HasUuid Trait in the original Application, just wanted to set specific Uuids for this test repro thats why i removed it.
But why does this
$ac->soldItems()->sync(['077df06d-1c38-4fd6-b141-3544c6ddbc27'])->save();
not work but this does
$ac->soldItems()->sync([$item->id])->save();
It throws back the item from the database.
Using the HasUuid Trait doesnt work on the real application nor on the test one for me if i use the string uuid instead of the $item->id
just wanted to set specific Uuids for this test repro thats why i removed it.
You need to override the $keyType
when your primary key is not an integer. Typically, that's done with protected $keyType = 'string';
.
but this does
$ac->soldItems()->sync([$item->id])->save();
This "works" because it sets $ac
's item_id
to 77
and then the relationship compares 77
to 77
internally.
Using the HasUuid Trait doesnt work on the real application nor on the test one for me if i use the string uuid instead of the $item->id
Your test repository doesn't work when you replace use HasFactory, HasJsonRelationships;
with use HasFactory, HasJsonRelationships, HasUuid;
in Item
?
sorry for the late response. I am currently doing more investigations around this, it seems that setting the keyType via a trait doesnt work. Neither via $model->keyType
nor via function getKeyType
...
Will post here again once i find more information!
I found a workaround, thats seems to fix the issue atleast for now, not sure if its something that could be handled by this library directly 🤔
public function __construct() {
parent::__construct();
$this->setKeyType('string');
}
Ive added this code to the Uuid Trait, as function getKeyType
was still throwing back int
for the keyType. Maybe it might be worth adding an error in for the sync method and others, that if the ID is a 36-string, it throws back that the keyType needs to be reflecting that? Not sure tbh, the error array_key_exists(): Argument #2 ($array) must be of type array, null given
just might be a bit to general
Did you try protected $keyType = 'string';
?
This works if used inside the models, but its quite tedious for multiple models! (or alteast the amount of models were i use uuids) (does not work in the trait)