Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[10.x] Allow for converting a HasMany to HasOne && MorphMany to MorphOne #46443

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/Illuminate/Database/Eloquent/Relations/HasMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@

class HasMany extends HasOneOrMany
{
/**
* Convert the relationship to a "has one" relationship.
*
* @return Illuminate\Database\Eloquent\Relations\HasOne
*/
public function one()
{
return new HasOne($this->getQuery(), $this->parent, $this->foreignKey, $this->localKey);
}

/**
* Get the results of the relationship.
*
Expand Down
18 changes: 18 additions & 0 deletions src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,24 @@ public function __construct(Builder $query, Model $farParent, Model $throughPare
parent::__construct($query, $throughParent);
}

/**
* Convert the relationship to a "has one through" relationship.
*
* @return \Illuminate\Database\Eloquent\Relations\HasOneThrough
*/
public function one()
{
return new HasOneThrough(
$this->getQuery(),
$this->farParent,
$this->throughParent,
$this->getFirstKeyName(),
$this->secondKey,
$this->getLocalKeyName(),
$this->getSecondLocalKeyName(),
);
}

/**
* Set the base constraints on the relation query.
*
Expand Down
10 changes: 10 additions & 0 deletions src/Illuminate/Database/Eloquent/Relations/MorphMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@

class MorphMany extends MorphOneOrMany
{
/**
* Convert the relationship to a "morph one" relationship.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
*/
public function one()
{
return new MorphOne($this->getQuery(), $this->getParent(), $this->morphType, $this->foreignKey, $this->localKey);
}

/**
* Get the results of the relationship.
*
Expand Down
81 changes: 81 additions & 0 deletions tests/Integration/Database/EloquentHasManyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace Illuminate\Tests\Integration\Database;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Facades\Schema;

class EloquentHasManyTest extends DatabaseTestCase
{
protected function defineDatabaseMigrationsAfterDatabaseRefreshed()
{
Schema::create('eloquent_has_many_test_users', function ($table) {
$table->id();
});

Schema::create('eloquent_has_many_test_logins', function ($table) {
$table->id();
$table->foreignId('eloquent_has_many_test_user_id');
$table->timestamp('login_time');
});
}

public function testCanGetHasOneFromHasManyRelationship()
{
$user = EloquentHasManyTestUser::create();

$user->logins()->create(['login_time' => now()]);

$this->assertInstanceOf(HasOne::class, $user->logins()->one());
}

public function testHasOneRelationshipFromHasMany()
{
$user = EloquentHasManyTestUser::create();

EloquentHasManyTestLogin::create([
'eloquent_has_many_test_user_id' => $user->id,
'login_time' => '2020-09-29',
]);
$latestLogin = EloquentHasManyTestLogin::create([
'eloquent_has_many_test_user_id' => $user->id,
'login_time' => '2023-03-14',
]);
$oldestLogin = EloquentHasManyTestLogin::create([
'eloquent_has_many_test_user_id' => $user->id,
'login_time' => '2010-01-01',
]);

$this->assertEquals($oldestLogin->id, $user->oldestLogin->id);
$this->assertEquals($latestLogin->id, $user->latestLogin->id);
}
}

class EloquentHasManyTestUser extends Model
{
protected $guarded = [];
public $timestamps = false;

public function logins(): HasMany
{
return $this->hasMany(EloquentHasManyTestLogin::class);
}

public function latestLogin(): HasOne
{
return $this->logins()->one()->latestOfMany('login_time');
}

public function oldestLogin(): HasOne
{
return $this->logins()->one()->oldestOfMany('login_time');
}
}

class EloquentHasManyTestLogin extends Model
{
protected $guarded = [];
public $timestamps = false;
}
31 changes: 31 additions & 0 deletions tests/Integration/Database/EloquentMorphManyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
namespace Illuminate\Tests\Integration\Database\EloquentMorphManyTest;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Illuminate\Tests\Integration\Database\DatabaseTestCase;
Expand Down Expand Up @@ -48,6 +50,25 @@ public function test_self_referencing_existence_query()

$this->assertEquals([1], $comments->pluck('id')->all());
}

public function testCanMorphOne()
{
$post = Post::create(['title' => 'Your favorite book by C.S. Lewis']);

Carbon::setTestNow('1990-02-02 12:00:00');
$oldestComment = tap((new Comment(['name' => 'The Allegory Of Love']))->commentable()->associate($post))->save();

Carbon::setTestNow('2000-07-02 09:00:00');
tap((new Comment(['name' => 'The Screwtape Letters']))->commentable()->associate($post))->save();

Carbon::setTestNow('2022-01-01 00:00:00');
$latestComment = tap((new Comment(['name' => 'The Silver Chair']))->commentable()->associate($post))->save();

$this->assertInstanceOf(MorphOne::class, $post->comments()->one());

$this->assertEquals($latestComment->id, $post->latestComment->id);
$this->assertEquals($oldestComment->id, $post->oldestComment->id);
}
}

class Post extends Model
Expand All @@ -61,6 +82,16 @@ public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}

public function latestComment(): MorphOne
{
return $this->comments()->one()->latestOfMany();
}

public function oldestComment(): MorphOne
{
return $this->comments()->one()->oldestOfMany();
}
}

class Comment extends Model
Expand Down