Skip to content
61 changes: 61 additions & 0 deletions src/Illuminate/Database/Eloquent/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,67 @@ public function hydrate(array $items)
}, $items));
}

/**
* Insert into the database after merging the model's default attributes, setting timestamps, and casting values.
*
* @param array<int, array<string, mixed>> $values
* @return bool
*/
public function fillAndInsert(array $values)
{
return $this->insert($this->fillForInsert($values));
}

/**
* Insert (ignoring errors) into the database after merging the model's default attributes, setting timestamps, and casting values.
*
* @param array<int, array<string, mixed>> $values
* @return int
*/
public function fillAndInsertOrIgnore(array $values)
{
return $this->insertOrIgnore($this->fillForInsert($values));
}

/**
* Insert a record into the database and get its ID after merging the model's default attributes, setting timestamps, and casting values.
*
* @param array<string, mixed> $values
* @return int
*/
public function fillAndInsertGetId(array $values)
{
return $this->insertGetId($this->fillForInsert([$values])[0]);
}

/**
* Enrich the given values by merging in the model's default attributes, adding timestamps, and casting values.
*
* @param array<int, array<string, mixed>> $values
* @return array<int, array<string, mixed>>
*/
public function fillForInsert(array $values)
{
if (empty($values)) {
return [];
}

if (! is_array(reset($values))) {
$values = [$values];
}

$this->model->unguarded(function () use (&$values) {
foreach ($values as $key => $rowValues) {
$values[$key] = tap(
$this->newModelInstance($rowValues),
fn ($model) => $model->setUniqueIds()
)->getAttributes();
}
});

return $this->addTimestampsToUpsertValues($values);
}

/**
* Create a collection of models from a raw query.
*
Expand Down
193 changes: 193 additions & 0 deletions tests/Database/DatabaseEloquentIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Database\Capsule\Manager as DB;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Illuminate\Database\Eloquent\ModelNotFoundException;
Expand All @@ -16,13 +17,15 @@
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Database\QueryException;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Pagination\AbstractPaginator as Paginator;
use Illuminate\Pagination\Cursor;
use Illuminate\Pagination\CursorPaginator;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Str;
use Illuminate\Tests\Integration\Database\Fixtures\Post;
use Illuminate\Tests\Integration\Database\Fixtures\User;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -80,6 +83,14 @@ protected function createSchema()
$table->timestamps();
});

$this->schema()->create('users_having_uuids', function (Blueprint $table) {
$table->id();
$table->uuid();
$table->string('name');
$table->tinyInteger('role');
$table->string('role_string');
});

foreach (['default', 'second_connection'] as $connection) {
$this->schema($connection)->create('users', function ($table) {
$table->increments('id');
Expand Down Expand Up @@ -187,6 +198,8 @@ protected function tearDown(): void
Eloquent::unsetConnectionResolver();

Carbon::setTestNow(null);
Str::createUuidsNormally();
DB::flushQueryLog();
}

/**
Expand Down Expand Up @@ -2461,6 +2474,147 @@ public function testTouchingBiDirectionalChaperonedModelUpdatesAllRelatedTimesta
}
}

public function testCanFillAndInsert()
{
DB::enableQueryLog();
Carbon::setTestNow('2025-03-15T07:32:00Z');

$this->assertTrue(EloquentTestUser::fillAndInsert([
['email' => '[email protected]', 'birthday' => null],
['email' => '[email protected]', 'birthday' => new Carbon('1980-01-01')],
['email' => '[email protected]', 'birthday' => '1987-11-01', 'created_at' => '2025-01-02T02:00:55', 'updated_at' => Carbon::parse('2025-02-19T11:41:13')],
]));

$this->assertCount(1, DB::getQueryLog());

$this->assertCount(3, $users = EloquentTestUser::get());

$users->take(2)->each(function (EloquentTestUser $user) {
$this->assertEquals(Carbon::parse('2025-03-15T07:32:00Z'), $user->created_at);
$this->assertEquals(Carbon::parse('2025-03-15T07:32:00Z'), $user->updated_at);
});

$tim = $users->firstWhere('email', '[email protected]');
$this->assertEquals(Carbon::parse('2025-01-02T02:00:55'), $tim->created_at);
$this->assertEquals(Carbon::parse('2025-02-19T11:41:13'), $tim->updated_at);

$this->assertNull($users[0]->birthday);
$this->assertInstanceOf(\DateTime::class, $users[1]->birthday);
$this->assertInstanceOf(\DateTime::class, $users[2]->birthday);
$this->assertEquals('1987-11-01', $users[2]->birthday->format('Y-m-d'));

DB::flushQueryLog();

$this->assertTrue(EloquentTestWithJSON::fillAndInsert([
['id' => 1, 'json' => ['album' => 'Keep It Like a Secret', 'release_date' => '1999-02-02']],
['id' => 2, 'json' => (object) ['album' => 'You In Reverse', 'release_date' => '2006-04-11']],
]));

$this->assertCount(1, DB::getQueryLog());

$this->assertCount(2, $testsWithJson = EloquentTestWithJSON::get());

$testsWithJson->each(function (EloquentTestWithJSON $testWithJson) {
$this->assertIsArray($testWithJson->json);
$this->assertArrayHasKey('album', $testWithJson->json);
});
}

public function testCanFillAndInsertWithUniqueStringIds()
{
Str::createUuidsUsingSequence([
'00000000-0000-7000-0000-000000000000',
'11111111-0000-7000-0000-000000000000',
'22222222-0000-7000-0000-000000000000',
]);

$this->assertTrue(ModelWithUniqueStringIds::fillAndInsert([
[
'name' => 'Taylor', 'role' => IntBackedRole::Admin, 'role_string' => StringBackedRole::Admin,
],
[
'name' => 'Nuno', 'role' => 3, 'role_string' => 'admin',
],
[
'name' => 'Dries', 'uuid' => 'bbbb0000-0000-7000-0000-000000000000',
],
[
'name' => 'Chris',
],
]));

$models = ModelWithUniqueStringIds::get();

$taylor = $models->firstWhere('name', 'Taylor');
$nuno = $models->firstWhere('name', 'Nuno');
$dries = $models->firstWhere('name', 'Dries');
$chris = $models->firstWhere('name', 'Chris');

$this->assertEquals(IntBackedRole::Admin, $taylor->role);
$this->assertEquals(StringBackedRole::Admin, $taylor->role_string);
$this->assertSame('00000000-0000-7000-0000-000000000000', $taylor->uuid);

$this->assertEquals(IntBackedRole::Admin, $nuno->role);
$this->assertEquals(StringBackedRole::Admin, $nuno->role_string);
$this->assertSame('11111111-0000-7000-0000-000000000000', $nuno->uuid);

$this->assertEquals(IntBackedRole::User, $dries->role);
$this->assertEquals(StringBackedRole::User, $dries->role_string);
$this->assertSame('bbbb0000-0000-7000-0000-000000000000', $dries->uuid);

$this->assertEquals(IntBackedRole::User, $chris->role);
$this->assertEquals(StringBackedRole::User, $chris->role_string);
$this->assertSame('22222222-0000-7000-0000-000000000000', $chris->uuid);
}

public function testFillAndInsertOrIgnore()
{
Str::createUuidsUsingSequence([
'00000000-0000-7000-0000-000000000000',
'11111111-0000-7000-0000-000000000000',
'22222222-0000-7000-0000-000000000000',
]);

$this->assertEquals(1, ModelWithUniqueStringIds::fillAndInsertOrIgnore([
[
'id' => 1, 'name' => 'Taylor', 'role' => IntBackedRole::Admin, 'role_string' => StringBackedRole::Admin,
],
]));

$this->assertSame(1, ModelWithUniqueStringIds::fillAndInsertOrIgnore([
[
'id' => 1, 'name' => 'Taylor', 'role' => IntBackedRole::Admin, 'role_string' => StringBackedRole::Admin,
],
[
'id' => 2, 'name' => 'Nuno',
],
]));

$models = ModelWithUniqueStringIds::get();
$this->assertSame('00000000-0000-7000-0000-000000000000', $models->firstWhere('name', 'Taylor')->uuid);
$this->assertSame(
['uuid' => '22222222-0000-7000-0000-000000000000', 'role' => IntBackedRole::User],
$models->firstWhere('name', 'Nuno')->only('uuid', 'role')
);
}

public function testFillAndInsertGetId()
{
Str::createUuidsUsingSequence([
'00000000-0000-7000-0000-000000000000',
]);

DB::enableQueryLog();

$this->assertIsInt($newId = ModelWithUniqueStringIds::fillAndInsertGetId([
'name' => 'Taylor',
'role' => IntBackedRole::Admin,
'role_string' => StringBackedRole::Admin,
]));
$this->assertCount(1, DB::getRawQueryLog());
$this->assertSame($newId, ModelWithUniqueStringIds::sole()->id);
}

/**
* Helpers...
*/
Expand Down Expand Up @@ -2786,3 +2940,42 @@ public function children()
return $this->hasMany(EloquentTouchingCategory::class, 'parent_id')->chaperone();
}
}

class ModelWithUniqueStringIds extends Eloquent
{
use HasUuids;

public $timestamps = false;

protected $table = 'users_having_uuids';

protected function casts()
{
return [
'role' => IntBackedRole::class,
'role_string' => StringBackedRole::class,
];
}

protected $attributes = [
'role' => IntBackedRole::User,
'role_string' => StringBackedRole::User,
];

public function uniqueIds()
{
return ['uuid'];
}
}

enum IntBackedRole: int
{
case User = 1;
case Admin = 3;
}

enum StringBackedRole: string
{
case User = 'user';
case Admin = 'admin';
}
Loading