diff --git a/src/Database/Eloquent/Relations/BelongsTo.php b/src/Database/Eloquent/Relations/BelongsTo.php index 7940a16..67df91e 100755 --- a/src/Database/Eloquent/Relations/BelongsTo.php +++ b/src/Database/Eloquent/Relations/BelongsTo.php @@ -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; @@ -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); diff --git a/src/Database/Eloquent/Relations/HasOneOrMany.php b/src/Database/Eloquent/Relations/HasOneOrMany.php index 4189d58..8f51888 100755 --- a/src/Database/Eloquent/Relations/HasOneOrMany.php +++ b/src/Database/Eloquent/Relations/HasOneOrMany.php @@ -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]; @@ -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)); @@ -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; diff --git a/tests/Enums/UserProfileType.php b/tests/Enums/UserProfileType.php new file mode 100644 index 0000000..8e56d4b --- /dev/null +++ b/tests/Enums/UserProfileType.php @@ -0,0 +1,9 @@ +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'); + }); } } diff --git a/tests/Models/User.php b/tests/Models/User.php index 8e20bac..c63b5cc 100644 --- a/tests/Models/User.php +++ b/tests/Models/User.php @@ -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; @@ -12,6 +13,7 @@ * @property Carbon $created_at * @property Carbon $updated_at * @property-read Allocation[] $allocations + * @property-read Collection $userProfiles * * @mixin \Illuminate\Database\Eloquent\Builder */ @@ -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); + } } diff --git a/tests/Models/UserProfile.php b/tests/Models/UserProfile.php new file mode 100644 index 0000000..5fe9a00 --- /dev/null +++ b/tests/Models/UserProfile.php @@ -0,0 +1,40 @@ + $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']); + } +} diff --git a/tests/Models/UserProfileText.php b/tests/Models/UserProfileText.php new file mode 100644 index 0000000..81a007b --- /dev/null +++ b/tests/Models/UserProfileText.php @@ -0,0 +1,34 @@ + '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']); + } +} diff --git a/tests/Unit/RelationWithEnumTest.php b/tests/Unit/RelationWithEnumTest.php new file mode 100644 index 0000000..3981f21 --- /dev/null +++ b/tests/Unit/RelationWithEnumTest.php @@ -0,0 +1,160 @@ +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); + }); + } +}