Skip to content

Commit 235e89c

Browse files
authored
Merge pull request #136 from hotwired-laravel/partials-path-pattern
Allow configuring partials path pattern
2 parents c3baf9d + 968ab51 commit 235e89c

File tree

5 files changed

+113
-3
lines changed

5 files changed

+113
-3
lines changed

docs/conventions.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,50 @@ When using this config, the redirection behavior will still happen, but the pack
3131

3232
You may want to split up your views in smaller chunks (aka. "partials"), such as a `comments/_comment.blade.php` to display a comment resource, or `comments/_form.blade.php` to display the form for both creating and updating comments. This allows you to reuse these _partials_ in [Turbo Streams](/docs/{{version}}/turbo-streams).
3333

34+
Alternatively, you may override the pattern to a `{plural}.partials.{singular}` convention for your partials location by calling the `Turbo::usePartialsSubfolderPattern()` method of the Turbo Facade from your `AppServiceProvider::boot()` method:
35+
36+
```php
37+
<?php
38+
39+
namespace App\Providers;
40+
41+
use HotwiredLaravel\TurboLaravel\Facades\Turbo;
42+
use Illuminate\Support\ServiceProvider;
43+
44+
class AppServiceProvider extends ServiceProvider
45+
{
46+
public function boot()
47+
{
48+
Turbo::usePartialsSubfolderPattern();
49+
}
50+
}
51+
```
52+
53+
You may also want to define your own pattern for partials, which you can do using the `Turbo::resolvePartialsUsing()` method. This method accepts either a string pattern or a Closure. If you pass a string pattern, you'll have two replaceholders available: `{singular}` and `{plural}`, which will get replaced with the model's singular and plural names, respectively, when the pattern is used. If you pass a Closure, you'll have the model instance available and you must return the view pattern using Laravel's dot notation convention. The pattern returned from the Closure will also get the placeholders applied, if you need that. Here's how you could manually define the partials subfolder pattern:
54+
55+
```php
56+
<?php
57+
58+
namespace App\Providers;
59+
60+
use HotwiredLaravel\TurboLaravel\Facades\Turbo;
61+
use Illuminate\Support\ServiceProvider;
62+
63+
class AppServiceProvider extends ServiceProvider
64+
{
65+
public function boot()
66+
{
67+
Turbo::resolvePartialsPathUsing('{plural}.partials.{singular}');
68+
69+
// Or...
70+
71+
Turbo::resolvePartialsPathUsing(fn ($model) => 'partials.{singular}');
72+
}
73+
}
74+
```
75+
76+
You may also want to define your own pattern, which you can do by either specifying a string where you have the `{plural}` and `{singular}` placeholders available, but you can also specify a Closure, which will receive the model instance. On that Closure, you must return a string with the view location using the dot convention of Laravel. For instance, the subfolder pattern sets the config value to `{plural}.partials.{singular}` instead of the default, which is `{plural}._{singular}`. These will resolve to `comments.partials.comment` and `comments._comment` views, respectively.
77+
3478
The models' partials (such as a `comments/_comment.blade.php` for a `Comment` model) may only rely on having a single `$comment` variable passed to them. That's because Turbo Stream Model Broadcasts - which is an _optional_ feature, by the way - relies on these conventions to figure out the partial for a given model when broadcasting and will also pass the model to such partial, using the class basename as the variable instance in _camelCase_. Again, this is optional, you can customize most of these things or create your own model broadcasting convention. Read the [Broadcasting](/docs/{{version}}/broadcasting) section to know more.
3579

3680
## Turbo Stream Channel Names

src/Facades/Turbo.php

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

33
namespace HotwiredLaravel\TurboLaravel\Facades;
44

5+
use Closure;
56
use HotwiredLaravel\TurboLaravel\Broadcasters\Broadcaster;
7+
use HotwiredLaravel\TurboLaravel\NamesResolver;
68
use HotwiredLaravel\TurboLaravel\Turbo as BaseTurbo;
79
use Illuminate\Database\Eloquent\Model;
810
use Illuminate\Support\Facades\Facade;
@@ -23,6 +25,16 @@
2325
*/
2426
class Turbo extends Facade
2527
{
28+
public static function usePartialsSubfolderPattern()
29+
{
30+
static::resolvePartialsPathUsing('{plural}.partials.{singular}');
31+
}
32+
33+
public static function resolvePartialsPathUsing(string|Closure $pattern)
34+
{
35+
NamesResolver::resolvePartialsPathUsing($pattern);
36+
}
37+
2638
protected static function getFacadeAccessor()
2739
{
2840
return BaseTurbo::class;

src/NamesResolver.php

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,20 @@
22

33
namespace HotwiredLaravel\TurboLaravel;
44

5+
use Closure;
56
use HotwiredLaravel\TurboLaravel\Models\Naming\Name;
67
use Illuminate\Database\Eloquent\Model;
78
use Illuminate\Support\Str;
89

910
class NamesResolver
1011
{
12+
protected static $partialsPathResolver = '{plural}._{singular}';
13+
14+
public static function resolvePartialsPathUsing(string|Closure $resolver)
15+
{
16+
static::$partialsPathResolver = $resolver;
17+
}
18+
1119
public static function resourceVariableName(Model $model): string
1220
{
1321
return Str::camel(Name::forModel($model)->singular);
@@ -17,9 +25,13 @@ public static function partialNameFor(Model $model): string
1725
{
1826
$name = Name::forModel($model);
1927

20-
$resource = $name->plural;
21-
$partial = $name->element;
28+
$replacements = [
29+
'{plural}' => $name->plural,
30+
'{singular}' => $name->element,
31+
];
32+
33+
$pattern = value(static::$partialsPathResolver, $model);
2234

23-
return "{$resource}._{$partial}";
35+
return str_replace(array_keys($replacements), array_values($replacements), value($pattern, $model));
2436
}
2537
}

tests/Models/NamesResolverTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace HotwiredLaravel\TurboLaravel\Tests\Models;
4+
5+
use HotwiredLaravel\TurboLaravel\Facades\Turbo;
6+
use HotwiredLaravel\TurboLaravel\NamesResolver;
7+
use HotwiredLaravel\TurboLaravel\Tests\TestCase;
8+
use Illuminate\Database\Eloquent\Model;
9+
use Workbench\Database\Factories\ArticleFactory;
10+
11+
class NamesResolverTest extends TestCase
12+
{
13+
/** @test */
14+
public function resolves_partial_naming()
15+
{
16+
$article = ArticleFactory::new()->make();
17+
18+
$this->assertEquals('articles._article', NamesResolver::partialNameFor($article));
19+
}
20+
21+
/** @test */
22+
public function resolves_partial_naming_using_subfolder()
23+
{
24+
$article = ArticleFactory::new()->make();
25+
26+
Turbo::usePartialsSubfolderPattern();
27+
28+
$this->assertEquals('articles.partials.article', NamesResolver::partialNameFor($article));
29+
}
30+
31+
/** @test */
32+
public function resolves_using_custom_closure()
33+
{
34+
$article = ArticleFactory::new()->make();
35+
36+
Turbo::resolvePartialsPathUsing(fn (Model $model) => 'partials.article');
37+
38+
$this->assertEquals('partials.article', NamesResolver::partialNameFor($article));
39+
}
40+
}

tests/TestCase.php

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

33
namespace HotwiredLaravel\TurboLaravel\Tests;
44

5+
use HotwiredLaravel\TurboLaravel\Facades\Turbo;
56
use HotwiredLaravel\TurboLaravel\Facades\TurboStream;
67
use Illuminate\Foundation\Testing\RefreshDatabase;
78
use Orchestra\Testbench\Concerns\WithWorkbench;
@@ -17,6 +18,7 @@ protected function setUp(): void
1718
parent::setUp();
1819

1920
TurboStream::fake();
21+
Turbo::resolvePartialsPathUsing('{plural}._{singular}');
2022
}
2123

2224
protected function defineEnvironment($app)

0 commit comments

Comments
 (0)