Skip to content

Commit 664c5d6

Browse files
[10.x] Add Support for SaveQuietly and Upsert with UUID/ULID Primary Keys (#46161)
* Improve how UUID/ULID is set when perform an Insert * Add UUID/ULID support when Upsert * formatting --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 791f8ea commit 664c5d6

File tree

6 files changed

+183
-37
lines changed

6 files changed

+183
-37
lines changed

src/Illuminate/Database/Eloquent/Builder.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1034,7 +1034,7 @@ public function upsert(array $values, $uniqueBy, $update = null)
10341034
}
10351035

10361036
return $this->toBase()->upsert(
1037-
$this->addTimestampsToUpsertValues($values),
1037+
$this->addTimestampsToUpsertValues($this->addUniqueIdsToUpsertValues($values)),
10381038
$uniqueBy,
10391039
$this->addUpdatedAtToUpsertColumns($update)
10401040
);
@@ -1124,6 +1124,29 @@ protected function addUpdatedAtColumn(array $values)
11241124
return $values;
11251125
}
11261126

1127+
/**
1128+
* Add unique IDs to the inserted values.
1129+
*
1130+
* @param array $values
1131+
* @return array
1132+
*/
1133+
protected function addUniqueIdsToUpsertValues(array $values)
1134+
{
1135+
if (! $this->model->usesUniqueIds()) {
1136+
return $values;
1137+
}
1138+
1139+
foreach ($this->model->uniqueIds() as $uniqueIdAttribute) {
1140+
foreach ($values as &$row) {
1141+
if (! array_key_exists($uniqueIdAttribute, $row)) {
1142+
$row = array_merge([$uniqueIdAttribute => $this->model->newUniqueId()], $row);
1143+
}
1144+
}
1145+
}
1146+
1147+
return $values;
1148+
}
1149+
11271150
/**
11281151
* Add timestamps to the inserted values.
11291152
*

src/Illuminate/Database/Eloquent/Concerns/HasUlids.php

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,23 @@
88
trait HasUlids
99
{
1010
/**
11-
* Boot the trait.
11+
* Initialize the trait.
1212
*
1313
* @return void
1414
*/
15-
public static function bootHasUlids()
15+
public function initializeHasUlids()
1616
{
17-
static::creating(function (self $model) {
18-
foreach ($model->uniqueIds() as $column) {
19-
if (empty($model->{$column})) {
20-
$model->{$column} = $model->newUniqueId();
21-
}
22-
}
23-
});
17+
$this->uniqueIds = true;
18+
}
19+
20+
/**
21+
* Get the columns that should receive a unique identifier.
22+
*
23+
* @return array
24+
*/
25+
public function uniqueIds()
26+
{
27+
return [$this->getKeyName()];
2428
}
2529

2630
/**
@@ -56,16 +60,6 @@ public function resolveRouteBindingQuery($query, $value, $field = null)
5660
return parent::resolveRouteBindingQuery($query, $value, $field);
5761
}
5862

59-
/**
60-
* Get the columns that should receive a unique identifier.
61-
*
62-
* @return array
63-
*/
64-
public function uniqueIds()
65-
{
66-
return [$this->getKeyName()];
67-
}
68-
6963
/**
7064
* Get the auto-incrementing key type.
7165
*
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace Illuminate\Database\Eloquent\Concerns;
4+
5+
trait HasUniqueIds
6+
{
7+
/**
8+
* Indicates if the model uses unique ids.
9+
*
10+
* @var bool
11+
*/
12+
public $uniqueIds = false;
13+
14+
/**
15+
* Determine if the model uses unique ids.
16+
*
17+
* @return bool
18+
*/
19+
public function usesUniqueIds()
20+
{
21+
return $this->uniqueIds;
22+
}
23+
24+
/**
25+
* Generate a unique keys for model.
26+
*
27+
* @return void
28+
*/
29+
public function setUniqueIds()
30+
{
31+
foreach ($this->uniqueIds() as $column) {
32+
if (empty($this->{$column})) {
33+
$this->{$column} = $this->newUniqueId();
34+
}
35+
}
36+
}
37+
38+
/**
39+
* Generate a new key for the model.
40+
*
41+
* @return string
42+
*/
43+
public function newUniqueId()
44+
{
45+
return null;
46+
}
47+
48+
/**
49+
* Get the columns that should receive a unique identifier.
50+
*
51+
* @return array
52+
*/
53+
public function uniqueIds()
54+
{
55+
return [];
56+
}
57+
}

src/Illuminate/Database/Eloquent/Concerns/HasUuids.php

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,39 +8,33 @@
88
trait HasUuids
99
{
1010
/**
11-
* Generate a primary UUID for the model.
11+
* Initialize the trait.
1212
*
1313
* @return void
1414
*/
15-
public static function bootHasUuids()
15+
public function initializeHasUuids()
1616
{
17-
static::creating(function (self $model) {
18-
foreach ($model->uniqueIds() as $column) {
19-
if (empty($model->{$column})) {
20-
$model->{$column} = $model->newUniqueId();
21-
}
22-
}
23-
});
17+
$this->uniqueIds = true;
2418
}
2519

2620
/**
27-
* Generate a new UUID for the model.
21+
* Get the columns that should receive a unique identifier.
2822
*
29-
* @return string
23+
* @return array
3024
*/
31-
public function newUniqueId()
25+
public function uniqueIds()
3226
{
33-
return (string) Str::orderedUuid();
27+
return [$this->getKeyName()];
3428
}
3529

3630
/**
37-
* Get the columns that should receive a unique identifier.
31+
* Generate a new UUID for the model.
3832
*
39-
* @return array
33+
* @return string
4034
*/
41-
public function uniqueIds()
35+
public function newUniqueId()
4236
{
43-
return [$this->getKeyName()];
37+
return (string) Str::orderedUuid();
4438
}
4539

4640
/**

src/Illuminate/Database/Eloquent/Model.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToSt
3030
Concerns\HasGlobalScopes,
3131
Concerns\HasRelationships,
3232
Concerns\HasTimestamps,
33+
Concerns\HasUniqueIds,
3334
Concerns\HidesAttributes,
3435
Concerns\GuardsAttributes,
3536
ForwardsCalls;
@@ -1286,6 +1287,10 @@ protected function performInsert(Builder $query)
12861287
$this->updateTimestamps();
12871288
}
12881289

1290+
if ($this->usesUniqueIds()) {
1291+
$this->setUniqueIds();
1292+
}
1293+
12891294
// If the model has an incrementing key, we can use the "insertGetId" method on
12901295
// the query builder, which will give us back the final inserted ID for this
12911296
// table from the database. Not all tables have to be incrementing though.

tests/Integration/Database/EloquentUniqueStringPrimaryKeysTest.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ protected function defineDatabaseMigrationsAfterDatabaseRefreshed()
2020
$table->timestamps();
2121
});
2222

23+
Schema::create('foo', function (Blueprint $table) {
24+
$table->uuid('id')->primary();
25+
$table->string('email')->unique();
26+
$table->string('name');
27+
$table->timestamps();
28+
});
29+
2330
Schema::create('posts', function (Blueprint $table) {
2431
$table->ulid('id')->primary();
2532
$table->ulid('foo');
@@ -73,6 +80,58 @@ public function testModelWithCustomUuidPrimaryKeyNameCanBeCreated()
7380

7481
$this->assertTrue(Str::isUuid($user->uuid));
7582
}
83+
84+
public function testModelWithUuidPrimaryKeyCanBeCreatedQuietly()
85+
{
86+
$user = new ModelWithUuidPrimaryKey();
87+
88+
$user->saveQuietly();
89+
90+
$this->assertTrue(Str::isUuid($user->id));
91+
$this->assertTrue(Str::isUuid($user->foo));
92+
$this->assertTrue(Str::isUuid($user->bar));
93+
}
94+
95+
public function testModelWithUlidPrimaryKeyCanBeCreatedQuietly()
96+
{
97+
$user = new ModelWithUlidPrimaryKey();
98+
99+
$user->saveQuietly();
100+
101+
$this->assertTrue(Str::isUlid($user->id));
102+
$this->assertTrue(Str::isUlid($user->foo));
103+
$this->assertTrue(Str::isUlid($user->bar));
104+
}
105+
106+
public function testModelWithoutUuidPrimaryKeyCanBeCreatedQuietly()
107+
{
108+
$user = new ModelWithoutUuidPrimaryKey();
109+
110+
$user->saveQuietly();
111+
112+
$this->assertTrue(is_int($user->id));
113+
$this->assertTrue(Str::isUuid($user->foo));
114+
$this->assertTrue(Str::isUuid($user->bar));
115+
}
116+
117+
public function testModelWithCustomUuidPrimaryKeyNameCanBeCreatedQuietly()
118+
{
119+
$user = new ModelWithCustomUuidPrimaryKeyName();
120+
121+
$user->saveQuietly();
122+
123+
$this->assertTrue(Str::isUuid($user->uuid));
124+
}
125+
126+
public function testUpsertWithUuidPrimaryKey()
127+
{
128+
ModelUpsertWithUuidPrimaryKey::create(['email' => 'foo', 'name' => 'bar']);
129+
ModelUpsertWithUuidPrimaryKey::create(['name' => 'bar1', 'email' => 'foo2']);
130+
131+
ModelUpsertWithUuidPrimaryKey::upsert([['email' => 'foo3', 'name' => 'bar'], ['name' => 'bar2', 'email' => 'foo2']], ['email']);
132+
133+
$this->assertEquals(3, ModelUpsertWithUuidPrimaryKey::count());
134+
}
76135
}
77136

78137
class ModelWithUuidPrimaryKey extends Eloquent
@@ -89,6 +148,20 @@ public function uniqueIds()
89148
}
90149
}
91150

151+
class ModelUpsertWithUuidPrimaryKey extends Eloquent
152+
{
153+
use HasUuids;
154+
155+
protected $table = 'foo';
156+
157+
protected $guarded = [];
158+
159+
public function uniqueIds()
160+
{
161+
return [$this->getKeyName()];
162+
}
163+
}
164+
92165
class ModelWithUlidPrimaryKey extends Eloquent
93166
{
94167
use HasUlids;

0 commit comments

Comments
 (0)