Laravel Best Practices

WhoAmI => notes.sohag.pro/author
Search for a command to run...

WhoAmI => notes.sohag.pro/author
Thanks for sharing these Laravel best practices. They make writing clean and efficient code so much easier! 😊😊
#Laravel #BestPractices #CleanCode #PHP #WebDevelopment
Code Smells in PHP and Laravel Ever walked into a room and immediately noticed something was off? Maybe it was a faint burning smell from the kitchen or the sound of a washing machine that didn't quite seem right. These warning signs in our daily liv...
I had a solid list of reasons my life wasn't moving faster, until a grainy old lecture pointed out the one name missing from it

The finale isn't a victory lap. It's the story of the control I shipped that did nothing, the footgun still sitting in my demo, and the handful of things I'd keep exactly as they are.

How do you hold a large payment for a second pair of eyes without ever letting the unapproved money touch a balance, and how do you stream that decision to the outside world without standing up a broker?

How do you show the total under a parent account when the whole system refuses to store a balance? A recursive query, a trigger that refuses to draw a circle, and a rule about what actually has to sum to zero.

Every time I wanted to change an FX rate I had to edit a file on the server and restart the app. So I moved rates and markup into a live admin API, and then audited it hard enough to find the bug that quietly undid the whole thing.

// ❌ Bad: N+1 Query Problem
@foreach (Post::all() as $post)
{{ $post->category->name }}
@endforeach
// ✅ Good: Eager Loading
$posts = Post::with('category')->get();
@foreach ($posts as $post)
{{ $post->category->name }}
@endforeach
// ✅ Even Better: Use Laravel's Lazy Loading when needed
Model::withoutLazy()->get(); // Disable lazy loading in production
// ✅ Good: Use Query Builder for complex queries
User::query()
->select(['name', 'email'])
->whereHas('posts', function ($query) {
$query->where('published', true);
})
->paginate();
// ✅ Good: Single Action Controller
class ShowDashboardController extends Controller
{
public function __invoke()
{
return view('dashboard', [
'metrics' => $this->getMetrics(),
]);
}
}
// ❌ Bad: Validation in Controller
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|max:255',
'body' => 'required',
]);
}
// ✅ Good: Dedicated Form Request
class StoreArticleRequest extends FormRequest
{
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'body' => ['required', 'string'],
'category_id' => ['required', 'exists:categories,id'],
];
}
}
public function store(StoreArticleRequest $request)
{
$article = Article::create($request->validated());
}
// ✅ Good: Single-Purpose Action Class
class PublishArticleAction
{
public function execute(Article $article): void
{
$article->published_at = now();
$article->status = ArticleStatus::Published;
$article->save();
event(new ArticlePublished($article));
}
}
// ✅ Good: Use DTOs for complex data structures
class ArticleData
{
public function __construct(
public readonly string $title,
public readonly string $content,
public readonly ?Carbon $publishDate = null,
) {}
public static function fromRequest(StoreArticleRequest $request): self
{
return new self(
title: $request->validated('title'),
content: $request->validated('content'),
publishDate: $request->validated('publish_date'),
);
}
}
// ✅ Good: Reusable Blade Components
// alert.blade.php
<x-alert type="{{ $type }}" :dismissible="$dismissible">
{{ $slot }}
</x-alert>
// Usage
<x-alert type="success" :dismissible="true">
Profile updated successfully!
</x-alert>
// ✅ Good: Modern Asset Management
// vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
export default defineConfig({
plugins: [
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: true,
}),
],
});
// In Blade
@vite(['resources/css/app.css', 'resources/js/app.js'])
// ✅ Good: Modern Testing with Pest
it('creates a new article', function () {
$response = post('/articles', [
'title' => 'My Article',
'content' => 'Content here',
]);
$response->assertCreated();
expect(Article::count())->toBe(1);
});
// ✅ Good: Modern Factory Usage
class ArticleFactory extends Factory
{
public function published(): self
{
return $this->state(fn (array $attributes) => [
'published_at' => now(),
'status' => ArticleStatus::Published,
]);
}
}
// Usage
Article::factory()->published()->create();
// ✅ Good: Use API Resources for Response Transformation
class ArticleResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'content' => $this->when($request->user()?->isAdmin(), $this->content),
'created_at' => $this->created_at->toISOString(),
];
}
}
// ✅ Good: Modern API Authentication
Route::middleware(['auth:sanctum'])->group(function () {
Route::apiResource('articles', ArticleController::class);
});
// ✅ Good: Smart Caching
public function show(Article $article)
{
return Cache::remember("articles.{$article->id}", now()->addHour(), fn () =>
ArticleResource::make($article)
);
}
// ✅ Good: Queue Heavy Operations
class ProcessPodcast implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function handle(): void
{
// Heavy processing here
}
}
// ✅ Good: Use Policies for Authorization
class ArticlePolicy
{
public function update(User $user, Article $article): bool
{
return $user->id === $article->user_id || $user->isAdmin();
}
}
// ❌ Bad: Direct Environment Variable Usage
$apiKey = env('API_KEY');
// ✅ Good: Use Configuration Files
// config/services.php
'api' => [
'key' => env('API_KEY'),
],
// Usage
$apiKey = config('services.api.key');
app/
├── Actions/ # Single-purpose action classes
├── Data/ # DTOs and other data objects
├── Events/ # Event classes
├── Listeners/ # Event listeners
├── Services/ # Complex business logic
├── Http/
│ ├── Controllers/
│ ├── Middleware/
│ └── Requests/ # Form requests
└── Models/ # Eloquent models
// ✅ Good: Register Bindings in Service Provider
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(PaymentGateway::class, StripeGateway::class);
}
}
# ✅ Good: Use Laravel Sail for consistent development environment
sail up
sail artisan migrate
sail test
# ✅ Good: Automated Testing and Deployment
name: Laravel
on:
push:
branches: [ main ]
jobs:
laravel-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
- name: Run Tests
run: |
composer install
php artisan test
The following table outlines the standard naming conventions for Laravel applications in 2024:
| Component | Convention | ✅ Good Example | ❌ Bad Example | Notes |
| Controller | singular | PostController | PostsController | Use singular form for resource controllers |
| Route | plural | posts/1 | post/1 | Use plural for resource routes |
| Named route | snake_case with dots | users.show_active | show-active-users | Consistent with Laravel's internal naming |
| Model | singular | User | Users | Always use singular for model names |
| hasOne/belongsTo relationship | singular | postComment | postComments | Reflects one-to-one nature |
| Other relationships | plural | postComments | postComment | Reflects one-to-many nature |
| Database table | plural | post_comments | post_comment | Use snake_case, plural form |
| Pivot table | alphabetical singular models | post_user | users_posts | Join singular model names with underscore |
| Model property | snake_case | $model->created_at | $model->createdAt | Follow Laravel's convention |
| Foreign key | singular with _id | post_id | PostId, posts_id | Always use snake_case |
| Primary key | 'id' | id | custom_id | Stick to Laravel defaults |
| Migration | datetime_action | 2024_01_16_000000_create_posts_table | 2024_01_16_posts | Include full timestamp |
| Method | camelCase | getAll() | get_all() | Follow PSR-12 |
| Resource controller method | table | store() | savePost() | Use standard resource names |
| Variable | camelCase | $postsWithAuthor | $posts_with_creator | Clear and descriptive |
| Collection | plural, descriptive | $activeUsers | $active | Clearly indicate content |
| Object | singular, descriptive | $activeUser | $users | Reflect single instance |
| Config index | snake_case | articles_enabled | ArticlesEnabled | Consistent with Laravel |
| View | kebab-case | show-filtered.blade.php | showFiltered.blade.php | Use hyphens for views |
| Config file | snake_case | google_calendar.php | googleCalendar.php | Use underscores |
| Interface | adjective/noun | AuthenticationInterface | IAuthentication | Follow PSR standards |
Consistency is Key
Stick to these conventions across your entire application
Use automated tools like PHP CS Fixer to enforce standards
Documentation
// ✅ Good: Clear, consistent naming
class PostController
{
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
}
File Structure
app/
├── Http/
│ └── Controllers/
│ └── PostController.php
├── Models/
│ └── Post.php
└── Views/
└── posts/
└── show-details.blade.php
Database Conventions
// ✅ Good: Migration naming
class CreatePostCommentsTable extends Migration
{
public function up()
{
Schema::create('post_comments', function (Blueprint $table) {
$table->id();
$table->foreignId('post_id')->constrained();
$table->timestamps();
});
}
}
Relationship Naming
// ✅ Good: Clear relationship naming
class Post extends Model
{
public function comments()
{
return $this->hasMany(Comment::class);
}
public function author()
{
return $this->belongsTo(User::class, 'user_id');
}
}
Following these naming conventions helps maintain consistency across your Laravel application and makes it easier for other developers to understand and work with your code. It also aligns with Laravel's built-in conventions, ensuring better integration with the framework's features and functionality.
Remember to:
Use PHP 8.2+ features where appropriate
Keep dependencies updated
Follow PSR-12 coding standards
Use static analysis tools like PHPStan
Implement proper logging and monitoring
Use Laravel Horizon for queue monitoring
Implement proper error handling and logging
Use Laravel Telescope for debugging in development
These practices reflect modern Laravel development as of 2024, incorporating new features and best practices from recent Laravel versions.