Skip to content

Commit

Permalink
Fixed crash when key contains BackedEnum
Browse files Browse the repository at this point in the history
  • Loading branch information
KentarouTakeda committed Jan 4, 2025
1 parent 1951dc8 commit 86290b3
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 4 deletions.
4 changes: 2 additions & 2 deletions src/Database/Eloquent/Relations/BelongsTo.php
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ public function match(array $models, Collection $results, $relation)
foreach ($results as $result) {
if (is_array($owner)) { //Check for multi-columns relationship
$dictKeyValues = array_map(function ($k) use ($result) {
return $result->{$k};
return $result->{$k} instanceof \BackedEnum ? $result->{$k}->value : $result->{$k};
}, $owner);

$dictionary[implode('-', $dictKeyValues)] = $result;
Expand All @@ -249,7 +249,7 @@ public function match(array $models, Collection $results, $relation)
foreach ($models as $model) {
if (is_array($foreign)) { //Check for multi-columns relationship
$dictKeyValues = array_map(function ($k) use ($model) {
return $model->{$k};
return $model->{$k} instanceof \BackedEnum ? $model->{$k}->value : $model->{$k};
}, $foreign);

$key = implode('-', $dictKeyValues);
Expand Down
12 changes: 10 additions & 2 deletions src/Database/Eloquent/Relations/HasOneOrMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ public function addConstraints()
$foreignKey = $this->getForeignKeyName();
$parentKeyValue = $this->getParentKey();

$parentKeyValue = is_array($parentKeyValue)
? array_map(function ($v) {
return $v instanceof \BackedEnum ? $v->value : $v;
}, $parentKeyValue)
: $parentKeyValue;

//If the foreign key is an array (multi-column relationship), we adjust the query.
if (is_array($this->foreignKey)) {
$allParentKeyValuesAreNull = array_unique($parentKeyValue) === [null];
Expand Down Expand Up @@ -227,7 +233,9 @@ protected function matchOneOrMany(array $models, Collection $results, $relation,
$key = $model->getAttribute($this->localKey);
//If the foreign key is an array, we know it's a multi-column relationship
//And we join the values to construct the dictionary key
$dictKey = is_array($key) ? implode('-', $key) : $key;
$dictKey = is_array($key) ? implode('-', array_map(function ($v) {
return $v instanceof \BackedEnum ? $v->value : $v;
}, $key)) : $key;

if (isset($dictionary[$dictKey])) {
$model->setRelation($relation, $this->getRelationValue($dictionary, $dictKey, $type));
Expand Down Expand Up @@ -257,7 +265,7 @@ protected function buildDictionary(Collection $results)
//If the foreign key is an array, we know it's a multi-column relationship...
if (is_array($foreign)) {
$dictKeyValues = array_map(function ($k) use ($result) {
return $result->{$k};
return $result->{$k} instanceof \BackedEnum ? $result->{$k}->value : $result->{$k};
}, $foreign);
//... so we join the values to construct the dictionary key
$dictionary[implode('-', $dictKeyValues)][] = $result;
Expand Down
8 changes: 8 additions & 0 deletions tests/Enums/UserProfileType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Awobaz\Compoships\Tests\Enums;

enum UserProfileType: string {
case Email = 'email';
case Url = 'url';
}
27 changes: 27 additions & 0 deletions tests/Migration.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,32 @@ public function up()
$table->uuid('pcid')->unique();
$table->string('code');
});

Capsule::schema()->create('user_profiles', function (Blueprint $table) {
$table->integer('user_id')
->unsigned();
$table->string('user_profile_type');
$table->timestamps();

$table->foreign('user_id')
->references('id')
->on('users')
->onUpdate('cascade')
->onDelete('cascade');
});

Capsule::schema()->create('user_profile_texts', function (Blueprint $table) {
$table->integer('user_id')
->unsigned();
$table->string('user_profile_type');
$table->string('user_profile_text');
$table->timestamps();

$table->foreign(['user_id', 'user_profile_type'])
->references(['user_id', 'user_profile_type'])
->on('user_profiles')
->onUpdate('cascade')
->onDelete('cascade');
});
}
}
7 changes: 7 additions & 0 deletions tests/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Awobaz\Compoships\Tests\Models;

use Awobaz\Compoships\Compoships;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;

Expand All @@ -12,6 +13,7 @@
* @property Carbon $created_at
* @property Carbon $updated_at
* @property-read Allocation[] $allocations
* @property-read Collection<UserProfile> $userProfiles
*
* @mixin \Illuminate\Database\Eloquent\Builder
*/
Expand All @@ -29,4 +31,9 @@ public function allocations()
{
return $this->hasMany(Allocation::class, ['user_id', 'booking_id'], ['id', 'booking_id']);
}

public function userProfiles()
{
return $this->hasMany(UserProfile::class);
}
}
40 changes: 40 additions & 0 deletions tests/Models/UserProfile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace Awobaz\Compoships\Tests\Models;

use Awobaz\Compoships\Compoships;
use Awobaz\Compoships\Tests\Enums\UserProfileType;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;

/**
* @property int $user_id
* @property UserProfileType $user_profile_type
* @property Carbon $created_at
* @property Carbon $updated_at
* @property-read Collection<UserProfileText> $userProfileTexts
*
* @mixin \Illuminate\Database\Eloquent\Builder
*/
class UserProfile extends Model
{
use Compoships;

// NOTE: we need this because Laravel 7 uses Carbon's method toJSON() instead of toDateTimeString()
protected $casts = [
'created_at' => 'datetime:Y-m-d H:i:s',
'updated_at' => 'datetime:Y-m-d H:i:s',
'user_profile_type' => UserProfileType::class,
];

public function user()
{
return $this->belongsTo(User::class);
}

public function userProfileTexts()
{
return $this->hasMany(UserProfileText::class, ['user_id', 'user_profile_type'], ['user_id', 'user_profile_type']);
}
}
34 changes: 34 additions & 0 deletions tests/Models/UserProfileText.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Awobaz\Compoships\Tests\Models;

use Awobaz\Compoships\Compoships;
use Awobaz\Compoships\Tests\Enums\UserProfileType;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;

/**
* @property int $user_id
* @property UserProfileType $user_profile_type
* @property string $user_profile_text
* @property Carbon $created_at
* @property Carbon $updated_at
*
* @mixin \Illuminate\Database\Eloquent\Builder
*/
class UserProfileText extends Model
{
use Compoships;

// NOTE: we need this because Laravel 7 uses Carbon's method toJSON() instead of toDateTimeString()
protected $casts = [
'created_at' => 'datetime:Y-m-d H:i:s',
'updated_at' => 'datetime:Y-m-d H:i:s',
'user_profile_type' => UserProfileType::class,
];

public function userProfile()
{
return $this->belongsTo(UserProfile::class, ['user_id', 'user_profile_type'], ['user_id', 'user_profile_type']);
}
}
161 changes: 161 additions & 0 deletions tests/Unit/RelationWithEnumTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<?php

namespace Awobaz\Compoships\Tests\Unit;

use Awobaz\Compoships\Tests\Enums\UserProfileType;
use Awobaz\Compoships\Tests\Models\User;
use Awobaz\Compoships\Tests\Models\UserProfile;
use Awobaz\Compoships\Tests\Models\UserProfileText;
use Awobaz\Compoships\Tests\TestCase\TestCase;
use Illuminate\Database\Eloquent\Model;

class RelationWithEnumTest extends TestCase
{
/**
* @var User
*/
private $user;

public function setUp(): void
{
if(getPHPVersion() < 8.1) {
$this->markTestSkipped('This test requires PHP 8.1 or higher');
}

Model::unguard();

$user = new User();
$user->save();

$user->userProfiles()->createMany([
['user_profile_type' => UserProfileType::Email],
['user_profile_type' => UserProfileType::Url],
]);

$user->userProfiles->each(function($userProfile) {
$userProfile->userProfileTexts()->createMany([
['user_profile_text' => 'text_1'],
['user_profile_text' => 'text_2'],
]);
});

$this->user = User::first();
}


/**
* @covers \Awobaz\Compoships\Database\Eloquent\Relations\HasOneOrMany
*/
public function test_lazy_load_has_many_relation_with_enum()
{
$this->assertNotEmpty($this->user->userProfiles);

$this->user->userProfiles->each(function (UserProfile $userProfile) {
$this->assertInstanceOf(UserProfileType::class, $userProfile->user_profile_type);
});
}

/**
* @covers \Awobaz\Compoships\Database\Eloquent\Relations\HasOneOrMany
*/
public function test_eager_load_has_many_relation_with_enum()
{
$this->user->load('userProfiles');

$this->assertNotEmpty($this->user->userProfiles);

$this->user->userProfiles->each(function (UserProfile $userProfile) {
$this->assertInstanceOf(UserProfileType::class, $userProfile->user_profile_type);
});
}

/**
* @covers \Awobaz\Compoships\Database\Eloquent\Relations\BelongsTo
*/
public function test_lazy_load_belongs_to_relation_with_enum()
{
$userProfiles = UserProfile::all();

$this->assertNotEmpty($userProfiles);

$userProfiles->each(function (UserProfile $userProfile) {
$this->assertInstanceOf(User::class, $userProfile->user);
});
}

/**
* @covers \Awobaz\Compoships\Database\Eloquent\Relations\BelongsTo
*/
public function test_eager_load_belongs_to_relation_with_enum()
{
$userProfiles = UserProfile::with('user')->get();

$this->assertNotEmpty($userProfiles);

$userProfiles->each(function (UserProfile $userProfile) {
$this->assertInstanceOf(User::class, $userProfile->user);
});
}

/**
* @covers \Awobaz\Compoships\Database\Eloquent\Relations\HasOneOrMany
*/
public function test_lazy_load_has_many_relation_with_enum_and_composite_key()
{
$this->assertNotEmpty($this->user->userProfiles);

$this->user->userProfiles->each(function (UserProfile $userProfile) {
$this->assertNotEmpty($userProfile->userProfileTexts);

$userProfile->userProfileTexts->each(function (UserProfileText $userProfileText) {
$this->assertInstanceOf(UserProfileType::class, $userProfileText->user_profile_type);
});
});
}

/**
* @covers \Awobaz\Compoships\Database\Eloquent\Relations\HasOneOrMany
*/
public function test_eager_load_has_many_relation_with_enum_and_composite_key()
{
$this->user->load('userProfiles.userProfileTexts');

$this->assertNotEmpty($this->user->userProfiles);

$this->user->userProfiles->each(function (UserProfile $userProfile) {
$this->assertNotEmpty($userProfile->userProfileTexts);

$userProfile->userProfileTexts->each(function (UserProfileText $userProfileText) {
$this->assertInstanceOf(UserProfileType::class, $userProfileText->user_profile_type);
});
});
}

/**
* @covers \Awobaz\Compoships\Database\Eloquent\Relations\BelongsTo
*/
public function test_lazy_load_belongs_to_relation_with_enum_and_composite_key()
{
$userProfileTexts = UserProfileText::all();

$this->assertNotEmpty($userProfileTexts);

$userProfileTexts->each(function (UserProfileText $userProfileText) {
$this->assertInstanceOf(UserProfile::class, $userProfileText->userProfile);
});
}

/**
* @covers \Awobaz\Compoships\Database\Eloquent\Relations\BelongsTo
*/
public function test_eager_load_belongs_to_relation_with_enum_and_composite_key()
{
$userProfileTexts = UserProfileText::with('userProfile')->get();

$this->assertNotEmpty($userProfileTexts);

$userProfileTexts->each(function (UserProfileText $userProfileText) {
$this->assertInstanceOf(UserProfile::class, $userProfileText->userProfile);
});
}
}

0 comments on commit 86290b3

Please sign in to comment.