All rules

Sheath rule

blade-no-logic-in-views

Complex business logic should not be in Blade templates.
Package
Core
Category
Blade
Default severity
warning by default
Auto-fix
Manual fix

#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 @php blocks: 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
  • @php blocks whose assignments chain three or more arithmetic operators on one line
  • @if/@unless/@elseif conditions with more than maxOperators logical 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