# Laravel Essentials Laravel Essentials is a PHP package that provides better defaults for Laravel applications. It enhances Laravel projects by enabling strict model behavior, automatic eager loading of relationships, immutable dates, and other security and performance improvements. The package is designed for Laravel 11+ and PHP 8.3+, offering a curated set of opinionated configurations that help developers avoid common pitfalls and follow best practices. This package modifies Laravel's default behavior through a configurable system of "Configurables" - classes that automatically apply settings when the service provider boots. Each feature can be individually enabled or disabled through the `config/essentials.php` configuration file, allowing developers to adopt only the defaults they need. The package also includes Artisan commands for generating action classes and publishing opinionated code quality configurations for Pint and Rector. ## Installation Installing the package via Composer ```bash composer require nunomaduro/essentials ``` ## Publishing Configuration Publishing the configuration file to customize settings ```bash php artisan vendor:publish --tag=essentials-config ``` ```php // config/essentials.php return [ NunoMaduro\Essentials\Configurables\ShouldBeStrict::class => true, NunoMaduro\Essentials\Configurables\AutomaticallyEagerLoadRelationships::class => true, NunoMaduro\Essentials\Configurables\ImmutableDates::class => true, NunoMaduro\Essentials\Configurables\ForceScheme::class => true, NunoMaduro\Essentials\Configurables\Unguard::class => false, 'environments' => [ NunoMaduro\Essentials\Configurables\ForceScheme::class => ['production'], ], ]; ``` ## Strict Model Configuration Enforcing strict mode on Eloquent models to prevent common bugs ```php // Automatically enabled by Essentials // config/essentials.php NunoMaduro\Essentials\Configurables\ShouldBeStrict::class => true, // Before: accessing missing attributes silently returns null $user = User::find(1); $invalid = $user->non_existent_attribute; // null (no error) // After: accessing missing attributes throws an exception $user = User::find(1); $invalid = $user->non_existent_attribute; // LogicException thrown // Before: lazy loading relationships causes N+1 queries $users = User::all(); foreach ($users as $user) { echo $user->posts->count(); // N+1 queries } // After: lazy loading is prevented $users = User::all(); foreach ($users as $user) { echo $user->posts->count(); // LazyLoadingViolationException thrown } // Proper usage with eager loading $users = User::with('posts')->get(); foreach ($users as $user) { echo $user->posts->count(); // Single query } ``` ## Automatic Eager Loading Automatically eager loading relationships defined in model's $with property ```php // Automatically enabled by Essentials // config/essentials.php NunoMaduro\Essentials\Configurables\AutomaticallyEagerLoadRelationships::class => true, // Define relationships to eager load in your model namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; class User extends Model { protected $with = ['posts', 'profile']; public function posts(): HasMany { return $this->hasMany(Post::class); } public function profile() { return $this->hasOne(Profile::class); } } // Usage: relationships are automatically loaded $users = User::all(); // Automatically includes posts and profile foreach ($users as $user) { echo $user->posts->count(); // No additional queries echo $user->profile->bio; // No additional queries } ``` ## Immutable Dates Configuration Using CarbonImmutable for all date operations to prevent mutations ```php // Automatically enabled by Essentials // config/essentials.php NunoMaduro\Essentials\Configurables\ImmutableDates::class => true, // Before: mutable dates can cause unexpected behavior $order = Order::find(1); $originalDate = $order->created_at; $newDate = $originalDate->addDays(7); // $originalDate is now modified - unexpected mutation // After: dates are immutable $order = Order::find(1); $originalDate = $order->created_at; // CarbonImmutable instance $newDate = $originalDate->addDays(7); // $originalDate remains unchanged, $newDate is a new instance echo $originalDate->toDateString(); // 2025-01-01 echo $newDate->toDateString(); // 2025-01-08 // Chain operations safely $date = now(); $tomorrow = $date->addDay(); $nextWeek = $date->addWeek(); // $date is never modified ``` ## Force HTTPS Scheme Forcing all generated URLs to use HTTPS ```php // Enabled by default in production environment // config/essentials.php NunoMaduro\Essentials\Configurables\ForceScheme::class => true, 'environments' => [ NunoMaduro\Essentials\Configurables\ForceScheme::class => ['production'], ], // All URLs generated by Laravel will use HTTPS route('user.profile', ['id' => 1]); // https://example.com/users/1 (not http://) url('/dashboard'); // https://example.com/dashboard asset('css/app.css'); // https://example.com/css/app.css // Useful in views Home ``` ## Prohibit Destructive Commands Blocking potentially destructive commands in production ```php // Automatically enabled by Essentials // config/essentials.php NunoMaduro\Essentials\Configurables\ProhibitDestructiveCommands::class => true, // In production environment php artisan migrate:fresh // RuntimeException: Destructive database commands are prohibited in production php artisan db:wipe // RuntimeException: Destructive database commands are prohibited in production // In local environment - commands work normally php artisan migrate:fresh // Migrations are dropped and re-run successfully // The protection is automatic based on app()->isProduction() ``` ## Set Default Password Validation Configuring default password validation rules ```php // Automatically enabled by Essentials // config/essentials.php NunoMaduro\Essentials\Configurables\SetDefaultPassword::class => true, // In production: strict password rules apply use Illuminate\Validation\Rules\Password; $request->validate([ 'password' => ['required', Password::defaults()], ]); // In production: min 12 chars, max 255 chars, not compromised // In local: no restrictions (null) // Custom validation in your request public function rules(): array { return [ 'email' => 'required|email', 'password' => ['required', Password::defaults()], 'password_confirmation' => 'required|same:password', ]; } // The password must be: // - At least 12 characters long (production only) // - Maximum 255 characters // - Not compromised in data breaches (production only) ``` ## Prevent Stray HTTP Requests Preventing unfaked HTTP requests during tests ```php // Automatically enabled during tests // config/essentials.php NunoMaduro\Essentials\Configurables\PreventStrayRequests::class => true, // In your test file use Illuminate\Support\Facades\Http; test('makes external API call', function () { // Without faking, this would throw RuntimeException // Http::get('https://api.example.com/users'); // Error! // Proper usage: fake the request first Http::fake([ 'api.example.com/*' => Http::response(['users' => []], 200), ]); $response = Http::get('https://api.example.com/users'); expect($response->json())->toHaveKey('users'); }); // Ensures all HTTP interactions are explicitly mocked // Prevents accidental real API calls during testing ``` ## Fake Sleep During Tests Faking sleep operations during tests to speed up execution ```php // Automatically enabled during tests // config/essentials.php NunoMaduro\Essentials\Configurables\FakeSleep::class => true, // In your application code use Illuminate\Support\Sleep; public function processWithRetry() { Sleep::for(5)->seconds(); // Would normally wait 5 seconds // Process something } // In your test - sleep is faked automatically test('processes with retry', function () { // This test runs instantly, no actual 5-second wait $result = app(Service::class)->processWithRetry(); expect($result)->toBeTrue(); }); // Assert sleep was called test('verifies sleep duration', function () { Sleep::fake(); app(Service::class)->processWithRetry(); Sleep::assertSlept(fn ($sleep) => $sleep->duration === 5); }); ``` ## Aggressive Asset Prefetching Configuring Vite to aggressively preload assets ```php // Automatically enabled by Essentials // config/essentials.php NunoMaduro\Essentials\Configurables\AggressivePrefetching::class => true, // In your Blade templates
@vite(['resources/css/app.css', 'resources/js/app.js'])