Sheath rule
blade-no-logic-in-views
Complex business logic should not be in Blade templates.
#Why
Complex calculations in templates are harder to reuse and test. Move them to a controller, view model, or accessor so the template only renders prepared data.
#Examples
#Bad
<!-- Loops in @php blocks -->
@php
$total = 0;
foreach ($items as $item) {
$total += $item->price * $item->quantity * (1 - $item->discount / 100);
}
@endphp
<!-- Exception handling belongs in the controller -->
@php
try {
$rate = $converter->rate($currency);
} catch (\RuntimeException $e) {
$rate = 1.0;
}
@endphp
<!-- Function definitions in a template -->
@php
function formatPrice($amount) {
return number_format($amount / 100, 2);
}
@endphp
<!-- Conditions with too many logical operators (maxOperators, default 2) -->
@if($user->isAdmin() && $user->isActive() && $order->isPending() && $order->total > 100)
<button>Approve</button>
@endif
#Good
<!-- Data passed from controller -->
@foreach($activeUsers as $user)
<p>{{ $user->name }}</p>
@endforeach
<!-- Calculation done in controller -->
<p>Total: {{ $formattedTotal }}</p>
<!-- Authorization handled elsewhere -->
@can('approve', $order)
<button>Approve Order</button>
@endcan
<!-- Simple presentation logic is OK -->
@if($users->isEmpty())
<p>No users found.</p>
@endif
<!-- Blade components for reusable presentation -->
<x-price :amount="$order->total" />
#Options
Use these options to set the expression threshold and include or exclude @php blocks.
| Option | Type | Default | Description |
|---|---|---|---|
maxOperators |
int |
2 |
Operators allowed in one expression before it counts. |
checkPhpBlocks |
bool |
true |
Whether to inspect @php blocks. |
checkConditions |
bool |
true |
Whether to inspect conditions such as @if and @elseif. |
<?php
'rules' => [
// Allow slightly longer expressions, and skip @php blocks entirely.
'blade-no-logic-in-views' => ['warning', [
'maxOperators' => 4,
'checkPhpBlocks' => false,
]],
],
#Reported Patterns
The following patterns are reported:
- Control-structure and declaration keywords in
@phpblocks:foreach,for,while,do,switch,function,class,trait,interface,try,catch,throw. Keywords inside string literals and comments are ignored. A keyword used after->,::, or$does not count, so$attributes->class([...])is treated as a method call rather than a class declaration @phpblocks whose assignments chain three or more arithmetic operators on one line@if/@unless/@elseifconditions with more thanmaxOperatorslogical operators (&&,||,and,or)
new ClassName(), static calls such as Model::where(), and service access
through app() or resolve() are not reported on their own. A one-line block
such as @php $svc = app(TaxService::class); @endphp therefore passes, even
though a controller is usually a better home for it.
#Where Logic Should Go
Move application logic to the layer responsible for that work.
| Logic Type | Where It Belongs |
|---|---|
| Database queries | Controllers, Repositories |
| Calculations | Controllers, Services |
| Business rules | Services, Policies |
| Formatting | View Models, Blade Components |
| Authorization | Policies, Gates |
#Controller Example
<?php
use App\Http\Controllers\Controller;
use App\Models\User;
final class UserController extends Controller
{
public function index()
{
$users = User::where('active', true)->get();
$items = request()->user()->cart->items;
$total = $this->calculateTotal($items);
return view('dashboard', [
'users' => $users,
'total' => $total,
]);
}
}
#Notes
- Simple conditionals (
@if,@foreach) are fine in views - Formatting helpers and Blade components are acceptable
- Consider View Models or Presenters for complex view logic
- This is a warning, not an error, as some edge cases exist
#Related Rules
- blade-no-php-tag - Prefer @php over raw PHP tags