I Love Sushi

Caleb Porzio has given us many great tools in the Laravel community. One that I've only read about and haven't used (until now) is Sushi.

I'm not going to go into detail about how Sushi works or how to use it, I just want to show you how I'm using it on Laradir and why it's better this way.

The Sponsors Component

This component is pretty simple, it just renders the sponsor logos you see on each page of the site.

The sponsors are just a collected array. The array contains the data for each sponsor:

1class Sponsors extends Component
2{
3 public function render()
4 {
5 $sponsors = collect([
6 [
7 'name' => 'Chief Tools',
8 'logo' => asset('storage/sponsors/chieftools.svg'),
9 'url' => 'https://chief.app/?ref=laradir.com',
10 'active' => true,
11 ],
12 [
13 'name' => 'Turbine UI',
14 'logo' => asset('storage/sponsors/turbine-ui.png'),
15 'url' => 'https://turbineui.com/?ref=laradir.com',
16 'active' => true,
17 ],
18 [
19 'name' => 'Wire in the Wild – Real World Laravel Livewire Projects',
20 'logo' => asset('storage/sponsors/wire-in-the-wild.png'),
21 'url' => 'https://wireinthewild.com/?ref=laradir.com',
22 'active' => true,
23 'classes' => '!h-12',
24 ],
25 [
26 'name' => 'Beyond Code',
27 'logo' => asset('storage/sponsors/beyond-code.png'),
28 'url' => 'https://beyondco.de/?utm_source=laradir&utm_medium=banner&utm_campaign=logo-sponsor',
29 'active' => true,
30 ],
31 
32 // More sponsors
33 
34 ])->transform(function ($sponsor): object {
35 return (object) $sponsor;
36 });
37 
38 if (! $this->all) {
39 $sponsors = $sponsors->filter(fn ($sponsor) => $sponsor->active);
40 }
41 
42 return view('sponsors', ['sponsors' => $sponsors]);
43 }
44}

There's nothing wrong with this code.

As you can see, each array item has almost all the same keys; they're quite homogenous, which makes the collection look eerily similar to a model collection, each item having the same attributes.

I'm also operating on the data in a way that smells like querying:

1 'logo' => asset('storage/sponsors/beyond-code.png'),
2 'url' => 'https://beyondco.de/?utm_source=laradir&utm_medium=banner&utm_campaign=logo-sponsor',
3 'active' => true,
4 ],
5 ])->transform(function ($sponsor): object {
6 return (object) $sponsor;
7 });
8 
9 if (! $this->all) {
10 $sponsors = $sponsors->filter(fn ($sponsor) => $sponsor->active);
11 }
12 
13 return view('sponsors', ['sponsors' => $sponsors]);
14}

But there are two key problems here:

  1. The data is trapped
    If I want to interrogate this collection of sponsors elsewhere, I now need to move some or all of this logic around with it.
  2. More complex interrogation requires more custom logic
    Given that this data is pretty simple and could just as easily have come from rows in a database table, it would be nice to have the full power of Eloquent at my fingertips without having to write loads of custom code.

php artisan make:migra...

STOP! I could do that. That would be the more 'standard' approach. But the thing is, this data doesn't change very often—and it doesn't need to.

Sponsors aren't coming and going every day. It's no hassle for me to edit the code and deploy a new version of the site when something changes here. It only takes a few minutes from start to finish.

What if I could just put the data in the model?

So Sushi Me

Sushi lets us do exactly that. And it's wonderful!

The data moves into the model class itself, so it's super easy to find.

Because it's moved out of the component I can use it in other places without having to find some place for it to live.

Now my component looks like this:

1use App\Models\Sponsor;
2 
3class Sponsors extends Component
4{
5 public function render()
6 {
7 $sponsors = $this->all ? Sponsor::all() : Sponsor::active()->get();
8 
9 return view('sponsors', ['sponsors' => $sponsors]);
10 }
11}

Look at that! Two lines of code. Simpler, more declarative, easier to understand. That's beautiful.

Some notes

  • The array items need to be fully homogenous - the exact same set of keys for every one.
  • If you want any sort of dynamism in the data, use the getRows() method instead of the protected $rows property.
  • Bask in the joy of raw fish on rice.

But is this good practice?

Probably not (to some people). But it works—very well! This is a very small dataset that isn't changing often, so I think this is fine.

Some datasets probably won't change at all, and so it makes even more sense for those (think lists of countries, languages etc). Why have that in your database?

Should it live in the model? That depends how far you want to take the Single Responsibility Principle, but I think it's fine. I think the colocation of the data and its logic in this case makes the DX so good that it's worth breaking some rules.

For now, this works well and I'm really happy with it. Maybe my needs will change in the future and it'll make more sense to have this data in the database, who knows.

Good thing is, all of my logic is already depending on the model now, so even if I do shift it to the database, most of my application code may be able to stay the same.


Want to see more Laradir code and contribute to it? Become a sponsor and you'll get access.