Although the Laravel framework has its own conventions, there are several others I tend to follow that (I think) make me more efficient, so I want to share them with the world. These are my personal “best practices” for conventions in Laravel models, Laravel Livewire components, and Tailwind CSS utility classes.
For Model classes, I like to use a specific hierarchy when ordering the methods inside of them. I order methods in Eloquent Models like this:
Model Event Hooks
=> Relations
=> Accessors & Mutators
=> Everything Else
By following a consistent hierarchy in the organization of your model methods, it is easier for other developers to understand and maintain your code. Placing model events / hooks first helps draw attention to important code that runs at specific points in the model's lifecycle. Similarly, placing relations before accessors and mutators can help make it clear which methods are responsible for interacting with other models, and which are responsible for modifying the current model's data.
To summarize, following these order guidelines for models has two big benefits:
Here’s an example model:
1class Podcast extends Model2{3 // Model event hooks and global scopes first4 protected static function booted()5 {6 static::creating(fn ($podcast) => $podcast->user_id = auth()->id());78 static::addGlobalScope('byUser', function (Builder $builder) {9 $builder->where('user_id', auth()->id());10 });11 }1213 // Relations next14 public function episodes()15 {16 return $this->hasMany(Episode::class);17 }1819 // Accessors and Mutators20 protected function isPublished(): Attribute21 {22 return Attribute::make(23 get: fn () => $this->published_at !== null,24 );25 }2627 // ... Everything else28}
1class Podcast extends Model2{3 // Model event hooks and global scopes first4 protected static function booted()5 {6 static::creating(fn ($podcast) => $podcast->user_id = auth()->id());78 static::addGlobalScope('byUser', function (Builder $builder) {9 $builder->where('user_id', auth()->id());10 });11 }1213 // Relations next14 public function episodes()15 {16 return $this->hasMany(Episode::class);17 }1819 // Accessors and Mutators20 protected function isPublished(): Attribute21 {22 return Attribute::make(23 get: fn () => $this->published_at !== null,24 );25 }2627 // ... Everything else28}
With Livewire components, I follow similar guidelines. It’s helpful to model the flow of the component, so a couple of things I usually do are:
Start with the mount()
method
Since mount()
is Livewire’s equivalent of a constructor, I like to keep it at the top (if your component has one, that is). This is the method that gets called when a component is instantiated, so it makes sense to keep it at the beginning for both visibility and ease of mentally modeling of the component lifecycle.
Computed properties after mount()
Mirroring how I think about Eloquent models, I find that it’s useful to keep the computed properties close to the top, after mount()
, but before any handler methods. Since these are accessed similarly to public properties, it makes more sense to me to have them grouped, rather than spread around the component.
Put the render()
method last
Similar to the mount method being first, the render()
method tends to be the last step in the component lifecycle, so I always keep it at the bottom. Think of it this way. The component is mounted, Livewire does the in-between stuff, then it renders the view. This (like most of these conventions) has the added benefit of allowing me to easily and predictably find the render method in big components.
Here’s an example Livewire component:
1<?php23namespace App\Http\Livewire;45use App\Models\Client;6use Livewire\Component;78class ClientCreate extends Component9{10 public Client $client;1112 protected $rules = [13 'client.name' => 'required',14 ];1516 // Mount first, after properties17 public function mount()18 {19 $this->client = Client::make();20 }2122 // Computed properties next23 public function getNicknameProperty()24 {25 return "The great".$this->client->name;26 }2728 // Methods and anything else29 public function create()30 {31 // ...32 }3334 // Always render() at the bottom35 public function render()36 {37 return view('livewire.client-create');38 }39}
1<?php23namespace App\Http\Livewire;45use App\Models\Client;6use Livewire\Component;78class ClientCreate extends Component9{10 public Client $client;1112 protected $rules = [13 'client.name' => 'required',14 ];1516 // Mount first, after properties17 public function mount()18 {19 $this->client = Client::make();20 }2122 // Computed properties next23 public function getNicknameProperty()24 {25 return "The great".$this->client->name;26 }2728 // Methods and anything else29 public function create()30 {31 // ...32 }3334 // Always render() at the bottom35 public function render()36 {37 return view('livewire.client-create');38 }39}
Lastly, there’s a couple of Tailwind CSS conventions I try to follow. This one is definitely more loose and it’s only because there’s no official integration for Laravel Blade with the Tailwind CSS Prettier plugin. Here’s how I organize utility classes:
Classes that affect spacing, layout, and position are usually first
Next, “general” classes. Ones affect the design, color, visuals (most utility classes)
Modifier classes (such as focus:
or hover:
next). These are often duplicates or extensions of the general classes that come with conditions, so having them follow the general base classes makes the most sense to me.
Dark mode classes are last in the overall class list, but always the first modifier if combined with other modifiers. For example, consider the following combination:
1<div class="hover:text-blue-500 dark:hover:text-blue-200"></div>
1<div class="hover:text-blue-500 dark:hover:text-blue-200"></div>
In this case I find that thinking of it as “if we’re in darkmode and hovering” is more straightforward than “if we’re hovering and oh, it’s darkmode”. To me, dark:
seems more like a blanket modifier, so I always make it the most prominent in the individual utility modifier stack. This convention also tends to follow the examples from Tailwind CSS.
Do you follow any unspoken conventions in your applications? Perhaps you disagree with the ones I use? Either way, I’d love to hear your thoughts on this topic. DM me, let’s talk!