Sign In Create Account
Laravel-Specific Rules

Laravel-Specific Rules

Framework conventions and best practices specific to Laravel applications.

Configuration

  • • Use kebab-case for configuration files
  • • Use snake_case for configuration keys
  • • Avoid env() outside configuration files

Configuration Examples

// config/payment-gateway.php - ✅ kebab-case filename
return [
  'api_key' => env('PAYMENT_API_KEY'), // ✅ snake_case keys
  'webhook_secret' => env('PAYMENT_WEBHOOK_SECRET'),
  'timeout_seconds' => 30,
  'supported_currencies' => ['USD', 'EUR', 'GBP'],
];

// ✅ Use config() in application code
class PaymentService
{
  public function __construct()
  {
    $this->apiKey = config('payment-gateway.api_key');
    $this->timeout = config('payment-gateway.timeout_seconds');
  }
}

// ❌ Avoid env() outside config files
class PaymentService
{
  public function __construct()
  {
    // This won't work properly with config caching
    $this->apiKey = env('PAYMENT_API_KEY');
  }
}

// config/custom-service.php - ✅ More examples
return [
  'default_provider' => env('CUSTOM_SERVICE_PROVIDER', 'local'),
  'rate_limiting' => [
    'max_requests' => env('CUSTOM_SERVICE_MAX_REQUESTS', 100),
    'window_minutes' => env('CUSTOM_SERVICE_WINDOW', 60),
  ],
  'features' => [
    'advanced_reporting' => env('ENABLE_ADVANCED_REPORTING', false),
    'beta_features' => env('ENABLE_BETA_FEATURES', false),
  ],
];

Routing

  • • Use kebab-case for public-facing URLs
  • • Prefer route tuple notation
  • • Use camelCase for route names
  • • Place HTTP verbs first when defining routes

Routing Examples

// routes/web.php - ✅ Preferred patterns
Route::get('/user-profile', [UserController::class, 'show'])
  ->name('userProfile');

Route::post('/password-reset', [PasswordController::class, 'reset'])
  ->name('passwordReset');

Route::get('/subscription-billing', [BillingController::class, 'index'])
  ->name('subscriptionBilling');

// ✅ Resource routes with kebab-case
Route::resource('blog-posts', BlogPostController::class);
// Results in: /blog-posts, /blog-posts/create, /blog-posts/{blog-post}, etc.

Route::resource('user-subscriptions', UserSubscriptionController::class);

// routes/api.php - ✅ API routes with HTTP verb first
Route::prefix('v1')->group(function () {
  Route::get('/users/{user}', [ApiController::class, 'getUser']);
  Route::post('/users', [ApiController::class, 'createUser']);
  Route::put('/users/{user}', [ApiController::class, 'updateUser']);
  Route::delete('/users/{user}', [ApiController::class, 'deleteUser']);
  
  Route::get('/users/{user}/subscriptions', [ApiController::class, 'getUserSubscriptions']);
  Route::post('/users/{user}/subscriptions', [ApiController::class, 'createSubscription']);
});

// ✅ Route model binding with kebab-case parameters
Route::get('/blog-posts/{blogPost}/comments/{comment}', [CommentController::class, 'show']);

// ❌ Avoid snake_case or camelCase in URLs
Route::get('/user_profile', [UserController::class, 'show']); // Wrong
Route::get('/userProfile', [UserController::class, 'show']); // Wrong

// ❌ Avoid closure routes in production
Route::get('/users', function () {
  return User::all(); // Hard to test and maintain
});

Controllers

  • • Use plural resource names
  • • Stick to default CRUD actions
  • • Extract additional actions to separate controllers

Controller Examples

// ✅ Preferred - plural resource names, standard CRUD
class UsersController extends Controller
{
  public function index(): View
  {
    return view('users.index', [
      'users' => User::paginate(20)
    ]);
  }
  
  public function create(): View
  {
    return view('users.create');
  }
  
  public function store(StoreUserRequest $request): RedirectResponse
  {
    $user = User::create($request->validated());
    
    return redirect()
      ->route('users.show', $user)
      ->with('success', 'User created successfully.');
  }
  
  public function show(User $user): View
  {
    return view('users.show', compact('user'));
  }
  
  public function edit(User $user): View
  {
    return view('users.edit', compact('user'));
  }
  
  public function update(UpdateUserRequest $request, User $user): RedirectResponse
  {
    $user->update($request->validated());
    
    return redirect()
      ->route('users.show', $user)
      ->with('success', 'User updated successfully.');
  }
  
  public function destroy(User $user): RedirectResponse
  {
    $user->delete();
    
    return redirect()
      ->route('users.index')
      ->with('success', 'User deleted successfully.');
  }
}

// ✅ Extract additional actions to separate controllers
class UserPasswordController extends Controller
{
  public function edit(User $user): View
  {
    return view('users.passwords.edit', compact('user'));
  }
  
  public function update(UpdatePasswordRequest $request, User $user): RedirectResponse
  {
    $user->update([
      'password' => Hash::make($request->password)
    ]);
    
    return redirect()
      ->route('users.show', $user)
      ->with('success', 'Password updated successfully.');
  }
}

class UserAvatarController extends Controller
{
  public function store(StoreAvatarRequest $request, User $user): RedirectResponse
  {
    $path = $request->file('avatar')->store('avatars', 'public');
    
    $user->update(['avatar_path' => $path]);
    
    return back()->with('success', 'Avatar updated successfully.');
  }
  
  public function destroy(User $user): RedirectResponse
  {
    if ($user->avatar_path) {
      Storage::disk('public')->delete($user->avatar_path);
      $user->update(['avatar_path' => null]);
    }
    
    return back()->with('success', 'Avatar removed successfully.');
  }
}

// ❌ Avoid - mixing additional actions in main controller
class UsersController extends Controller
{
  // ... CRUD methods ...
  
  public function updatePassword(User $user) { } // Should be separate
  public function uploadAvatar(User $user) { } // Should be separate
  public function sendWelcomeEmail(User $user) { } // Should be separate
  public function exportToCsv() { } // Should be separate
}

Validation

  • • Use array notation for validation rules
  • • Use snake_case for custom validation rules
  • • Prefer Form Request classes for complex validation

Validation Examples

// ✅ Preferred - array notation in Form Requests
class StoreUserRequest extends FormRequest
{
  public function rules(): array
  {
    return [
      'name' => ['required', 'string', 'max:255'],
      'email' => ['required', 'email', 'unique:users'],
      'password' => ['required', 'min:8', 'confirmed'],
      'date_of_birth' => ['nullable', 'date', 'before:today'],
      'phone' => ['nullable', 'string', 'regex:/^\+?[1-9]\d{1,14}$/'],
    ];
  }
  
  public function messages(): array
  {
    return [
      'email.unique' => 'This email address is already registered.',
      'phone.regex' => 'Please enter a valid phone number.',
    ];
  }
}

// ✅ Custom validation rule - snake_case filename and class
// app/Rules/ValidDomainName.php
class ValidDomainName implements Rule
{
  public function passes($attribute, $value): bool
  {
    return preg_match('/^[a-z0-9\-\.]+\.[a-z]{2,}$/i', $value);
  }
  
  public function message(): string
  {
    return 'The :attribute must be a valid domain name.';
  }
}

// Usage in Form Request
class StoreWebsiteRequest extends FormRequest
{
  public function rules(): array
  {
    return [
      'name' => ['required', 'string', 'max:255'],
      'url' => ['required', 'url'],
      'domain' => ['required', new ValidDomainName],
    ];
  }
}

// ✅ Simple validation in controller (when Form Request is overkill)
class SimpleController extends Controller
{
  public function store(Request $request)
  {
    $validated = $request->validate([
      'title' => ['required', 'string', 'max:255'],
      'content' => ['required', 'string'],
    ]);
    
    // Process validated data...
  }
}

// ❌ Avoid - pipe notation (less readable and flexible)
public function rules(): array
{
  return [
    'name' => 'required|string|max:255',
    'email' => 'required|email|unique:users',
    'password' => 'required|min:8|confirmed',
  ];
}

// ❌ Avoid - inline validation for complex rules
public function store(Request $request)
{
  $request->validate([
    'name' => 'required|string|max:255',
    'email' => 'required|email|unique:users,email,' . $request->id,
    'password' => 'required|min:8|confirmed',
    'role' => 'required|in:admin,user,moderator',
    'permissions.*' => 'exists:permissions,id',
    'profile.bio' => 'nullable|string|max:1000',
    'profile.website' => 'nullable|url',
  ]); // Too complex for inline validation
}

Authorization

  • • Use camelCase for policies
  • • Prefer standard CRUD words for ability names
  • • Keep policy methods focused and single-purpose

Authorization Examples

// ✅ Preferred - UserPolicy with camelCase methods
class UserPolicy
{
  public function viewAny(User $user): bool
  {
    return $user->hasRole('admin') || $user->hasRole('moderator');
  }
  
  public function view(User $user, User $model): bool
  {
    return $user->id === $model->id 
      || $user->hasRole('admin')
      || ($user->hasRole('moderator') && !$model->hasRole('admin'));
  }
  
  public function create(User $user): bool
  {
    return $user->hasRole('admin');
  }
  
  public function update(User $user, User $model): bool
  {
    return $user->id === $model->id || $user->hasRole('admin');
  }
  
  public function delete(User $user, User $model): bool
  {
    return $user->hasRole('admin') && $user->id !== $model->id;
  }
  
  public function restore(User $user, User $model): bool
  {
    return $user->hasRole('admin');
  }
  
  public function forceDelete(User $user, User $model): bool
  {
    return $user->hasRole('admin') && $user->id !== $model->id;
  }
}

// ✅ Usage in controllers
class UsersController extends Controller
{
  public function index(): View
  {
    $this->authorize('viewAny', User::class);
    
    return view('users.index', [
      'users' => User::paginate(20)
    ]);
  }
  
  public function show(User $user): View
  {
    $this->authorize('view', $user);
    
    return view('users.show', compact('user'));
  }
  
  public function edit(User $user): View
  {
    $this->authorize('update', $user);
    
    return view('users.edit', compact('user'));
  }
}

// ✅ Usage in Blade templates
@can('update', $user)
  <a href="{{ route('users.edit', $user) }}" class="btn btn-primary">
    Edit User
  </a>
@endcan

@can('delete', $user)
  <form method="POST" action="{{ route('users.destroy', $user) }}">
    @csrf
    @method('DELETE')
    <button type="submit" class="btn btn-danger">Delete</button>
  </form>
@endcan

// ❌ Avoid - non-standard ability names
class UserPolicy
{
  public function showUser(User $user, User $model): bool { } // Use 'view'
  public function editUser(User $user, User $model): bool { } // Use 'update'
  public function removeUser(User $user, User $model): bool { } // Use 'delete'
}