Skip to content

Commit d31e0ad

Browse files
[9.x] Validate ulid before route binding query (#44995)
* validate uuid before route binding query * validate ulid in `resolveRouteBindingQuery` and add tests * format * Add throw in doc-block * format * Format * format * Fix format * Fix the `timestamps`
1 parent 1d18fae commit d31e0ad

File tree

2 files changed

+83
-0
lines changed

2 files changed

+83
-0
lines changed

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Illuminate\Database\Eloquent\Concerns;
44

5+
use Illuminate\Database\Eloquent\ModelNotFoundException;
56
use Illuminate\Support\Str;
67

78
trait HasUlids
@@ -32,6 +33,29 @@ public function newUniqueId()
3233
return strtolower((string) Str::ulid());
3334
}
3435

36+
/**
37+
* Retrieve the model for a bound value.
38+
*
39+
* @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation $query
40+
* @param mixed $value
41+
* @param string|null $field
42+
* @return \Illuminate\Database\Eloquent\Relations\Relation
43+
*
44+
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
45+
*/
46+
public function resolveRouteBindingQuery($query, $value, $field = null)
47+
{
48+
if ($field && in_array($field, $this->uniqueIds()) && ! Str::isUlid($value)) {
49+
throw (new ModelNotFoundException)->setModel(get_class($this), $value);
50+
}
51+
52+
if (! $field && in_array($this->getRouteKeyName(), $this->uniqueIds()) && ! Str::isUlid($value)) {
53+
throw (new ModelNotFoundException)->setModel(get_class($this), $value);
54+
}
55+
56+
return parent::resolveRouteBindingQuery($query, $value, $field);
57+
}
58+
3559
/**
3660
* Get the columns that should receive a unique identifier.
3761
*

tests/Integration/Routing/ImplicitModelRouteBindingTest.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Illuminate\Tests\Integration\Routing;
44

5+
use Illuminate\Database\Eloquent\Concerns\HasUlids;
56
use Illuminate\Database\Eloquent\Concerns\HasUuids;
67
use Illuminate\Database\Eloquent\Model;
78
use Illuminate\Database\Eloquent\SoftDeletes;
@@ -41,6 +42,13 @@ protected function defineDatabaseMigrations(): void
4142
$table->timestamps();
4243
});
4344

45+
Schema::create('tags', function (Blueprint $table) {
46+
$table->ulid('id')->primary();
47+
$table->string('slug');
48+
$table->integer('post_id');
49+
$table->timestamps();
50+
});
51+
4452
Schema::create('comments', function (Blueprint $table) {
4553
$table->uuid('id')->primary();
4654
$table->string('slug');
@@ -51,6 +59,7 @@ protected function defineDatabaseMigrations(): void
5159
$this->beforeApplicationDestroyed(function () {
5260
Schema::dropIfExists('users');
5361
Schema::dropIfExists('posts');
62+
Schema::dropIfExists('tags');
5463
Schema::dropIfExists('comments');
5564
});
5665
}
@@ -261,6 +270,37 @@ public function testImplicitRouteBindingChildHasUuids()
261270
$response = $this->getJson("/user/{$user->id}/comment-slug/{$comment->slug}");
262271
$response->assertJsonFragment(['id' => $comment->id]);
263272
}
273+
274+
public function testImplicitRouteBindingChildHasUlids()
275+
{
276+
$user = ImplicitBindingUser::create(['name' => 'Michael Nabil']);
277+
$post = ImplicitBindingPost::create(['user_id' => $user->id]);
278+
$tag = ImplicitBindingTag::create([
279+
'slug' => 'slug',
280+
'post_id' => $post->id,
281+
]);
282+
283+
config(['app.key' => str_repeat('a', 32)]);
284+
285+
$function = function (ImplicitBindingPost $post, ImplicitBindingTag $tag) {
286+
return [$post, $tag];
287+
};
288+
289+
Route::middleware(['web'])->group(function () use ($function) {
290+
Route::get('/post/{post}/tag/{tag}', $function);
291+
Route::get('/post/{post}/tag-id/{tag:id}', $function);
292+
Route::get('/post/{post}/tag-slug/{tag:slug}', $function);
293+
});
294+
295+
$response = $this->getJson("/post/{$post->id}/tag/{$tag->slug}");
296+
$response->assertJsonFragment(['id' => $tag->id]);
297+
298+
$response = $this->getJson("/post/{$post->id}/tag-id/{$tag->id}");
299+
$response->assertJsonFragment(['id' => $tag->id]);
300+
301+
$response = $this->getJson("/post/{$post->id}/tag-slug/{$tag->slug}");
302+
$response->assertJsonFragment(['id' => $tag->id]);
303+
}
264304
}
265305

266306
class ImplicitBindingUser extends Model
@@ -287,6 +327,25 @@ class ImplicitBindingPost extends Model
287327
public $table = 'posts';
288328

289329
protected $fillable = ['user_id'];
330+
331+
public function tags()
332+
{
333+
return $this->hasMany(ImplicitBindingTag::class, 'post_id');
334+
}
335+
}
336+
337+
class ImplicitBindingTag extends Model
338+
{
339+
use HasUlids;
340+
341+
public $table = 'tags';
342+
343+
protected $fillable = ['slug', 'post_id'];
344+
345+
public function getRouteKeyName()
346+
{
347+
return 'slug';
348+
}
290349
}
291350

292351
class ImplicitBindingComment extends Model

0 commit comments

Comments
 (0)