ivanfuhr / essentials
Just better defaults for your Laravel projects.
Fund package maintenance!
Requires
- php: ^8.3.0
- laravel/framework: ^11.44.2|^12.52.0|^13.5
- monolog/monolog: ^3.6
Requires (Dev)
- laravel/pint: ^1.29.0
- orchestra/testbench: ^9.0|^10.9.0|^11.1
- pestphp/pest: ^3.8.5|^4.6.2
- pestphp/pest-plugin-laravel: ^3.2.0|^4.0.0
- pestphp/pest-plugin-type-coverage: ^3.6.1|^4.0.4
- phpstan/phpstan: ^2.1.50
- rector/rector: ^2.4.2
- symfony/var-dumper: ^7.4.4|^8.0.8
This package is auto-updated.
Last update: 2026-06-22 01:14:35 UTC
README
Essentials provide better defaults for your Laravel applications including strict models, automatically eagerly loaded relationships, immutable dates, a lightweight Result type for service outcomes, and more!
Requires PHP 8.3+, Laravel 11+.
Note: This package modifies the default behavior of Laravel. It is recommended to use it in new projects or when you are comfortable with the changes it introduces.
Installation
⚡️ Get started by requiring the package using Composer:
composer require ivanfuhr/essentials
Features
All features are optional and configurable in config/essentials.php.
You may publish the configuration file with:
php artisan vendor:publish --tag=essentials-config
Table of Contents
- Strict Models
- Auto Eager Loading
- Optional Unguarded Models
- Immutable Dates
- Force HTTPS
- Safe Console
- Prevent Stray Requests
- Fake Sleep
- Result
- Artisan Commands
- GitHub Issue Logger
- Credits
- License
✅ Strict Models
Improves how Eloquent handles undefined attributes, lazy loading, and invalid assignments.
- Accessing a missing attribute throws an error.
- Lazy loading is blocked unless explicitly allowed.
- Setting undefined attributes throws instead of failing silently.
Why: Avoids subtle bugs and makes model behavior easier to reason about.
⚡️ Auto Eager Loading
Automatically eager loads relationships defined in the model's $with property.
Why: Reduces N+1 query issues and improves performance without needing with() everywhere.
🔓 Optional Unguarded Models
Disables Laravel's mass assignment protection globally (opt-in).
Why: Useful in trusted or local environments where you want to skip defining $fillable.
🕒 Immutable Dates
Uses CarbonImmutable instead of mutable date objects across your app.
Why: Prevents unexpected date mutations and improves predictability.
🔒 Force HTTPS
Forces all generated URLs to use https://.
Why: Ensures all traffic uses secure connections by default.
🛑 Safe Console
Blocks potentially destructive Artisan commands in production (e.g., migrate:fresh).
Why: Prevents accidental data loss and adds a safety net in sensitive environments.
🔄 Prevent Stray Requests
Configures Laravel Http Facade to prevent stray requests.
Why: Ensure every HTTP calls during tests have been explicitly faked.
😴 Fake Sleep
Configures Laravel Sleep Facade to be faked.
Why: Avoid unexpected sleep during testing cases.
📦 Result
A small, framework-agnostic success/failure wrapper for service and action outcomes. Expected business failures are modeled as PHP enums, not thrown exceptions — so invalid email, duplicate records, and similar cases stay explicit and easy to branch on.
Namespace: IvanFuhr\Essentials\Result\Result
No configuration or service provider setup is required. Composer autoloads the class and registers global helpers immediately (no service provider).
PHPStan generics: annotate actions with @return Result<YourModel, YourFailureEnum> (or the native return type stays Result and the PHPDoc carries the types). After successful() / failed(), value() and failure() are narrowed to the success value and failure enum respectively.
Defining failure enums
enum CreateUserError { case InvalidEmail; case EmailAlreadyExists; }
Use a dedicated enum per use case (or domain area). Backed enums (string or int) work well when you need stable codes for APIs or translations.
Creating results
Global helpers are available as soon as the package is installed:
// Success with a value (any type) $result = success($user); // Failure with an enum case $result = fail(CreateUserError::InvalidEmail);
You can also use the class directly:
use IvanFuhr\Essentials\Result\Result; $result = Result::success($user); $result = Result::fail(CreateUserError::InvalidEmail);
Note: The
fail()helper andResult::fail()factory exist because PHP does not allow a staticfailure()factory and an instancefailure()getter on the same class. The getter returns the enum case:$result->failure().
Checking state
| Method | Description |
|---|---|
successful() |
true when the result holds a success value |
failed() |
true when the result holds a failure enum |
value() |
Returns the success value (call only when successful() is true) |
valueOr($default) |
Returns the success value, or $default on failure |
failure() |
Returns the failure UnitEnum (call only when failed() is true) |
Prefer whenSuccessful() / whenFailed() for branching. Use value() and failure() after checking state, or valueOr() when a default is enough.
if ($result->successful()) { $user = $result->value(); } $guest = $result->valueOr(null); if ($result->failed()) { $error = $result->failure(); // CreateUserError enum case }
Fluent handling
Chain handlers on the same result. Only the first matching handler runs; later handlers are skipped until you start a new chain.
| Method | Description |
|---|---|
whenSuccessful(callable $callback) |
Runs only on success; receives the value |
whenFailed(UnitEnum $expectedFailure, callable $callback) |
Runs only when the failure enum equals $expectedFailure (===) |
otherwise(callable $callback) |
Runs only if no earlier handler matched |
Typical usage in a controller or action:
enum CreateUserError { case InvalidEmail; case EmailAlreadyExists; } return $this->createUser->handle($email) ->whenSuccessful(fn (User $user) => redirect()->route('users.show', $user)) ->whenFailed(CreateUserError::InvalidEmail, fn () => back()->withErrors(['email' => 'Invalid email.'])) ->whenFailed(CreateUserError::EmailAlreadyExists, fn () => back()->withErrors(['email' => 'Email already in use.'])) ->otherwise(fn () => abort(500));
Service returning a Result:
final readonly class CreateUser { public function handle(string $email): Result { if (! filter_var($email, FILTER_VALIDATE_EMAIL)) { return fail(CreateUserError::InvalidEmail); } if (User::query()->where('email', $email)->exists()) { return fail(CreateUserError::EmailAlreadyExists); } return success(User::create(['email' => $email])); } }
Why: Keeps happy paths and expected failures explicit, improves testability, and avoids try/catch for business rules that are not exceptional.
Tips:
Result::fail()only acceptsUnitEnum— define one enum per operation or bounded context.whenFailed()compares enum cases with===; pass the same case you used inResult::fail().- Use
otherwise()for unhandled enum cases (e.g. a new case you have not mapped yet). - Handlers are for side effects (redirects, logging, mapping to HTTP). Use
value()/failure()when you need the payload after checkingsuccessful()/failed().
🏗️ Artisan Commands
make:action
Quickly generates action classes in your Laravel application:
php artisan make:action CreateUserAction
This creates a clean action class at app/Actions/CreateUserAction.php:
<?php declare(strict_types=1); namespace App\Actions; final readonly class CreateUserAction { /** * Execute the action. */ public function handle(): void { DB::transaction(function (): void { // }); } }
Actions help organize business logic in dedicated classes, promoting single responsibility and cleaner controllers.
Database Backups
PostgreSQL backup, restore, and retention commands powered by pg_dump and pg_restore.
php artisan db:backup php artisan db:restore php artisan db:backups:prune
Configure disk, directory, retention, and binary paths under backup in config/essentials.php.
translations:extract
Scans PHP and Blade files for translation strings and generates or updates a JSON language file:
php artisan translations:extract php artisan translations:extract app/Filament en
Supports __(), trans(), trans_choice(), @lang(), and common attribute patterns like #[Title(...)] and #[Validate(as: ...)].
Configuration
All features are configurable through the essentials.php config file. By default, most features are enabled, but you can disable any feature by setting its configuration value to false:
// config/essentials.php return [ 'configurables' => [ IvanFuhr\Essentials\Configurables\ShouldBeStrict::class => true, IvanFuhr\Essentials\Configurables\Unguard::class => false, ], 'backup' => [ 'disk' => env('DB_BACKUP_DISK'), 'directory' => env('DB_BACKUP_DIRECTORY', 'backups'), 'retention_days' => (int) env('DB_BACKUP_RETENTION_DAYS', 30), 'pg_dump_binary' => env('PG_DUMP_PATH', 'pg_dump'), 'pg_restore_binary' => env('PG_RESTORE_PATH', 'pg_restore'), ], ];
You may also publish the stubs used by this package:
php artisan vendor:publish --tag=essentials-stubs
🐙 GitHub Issue Logger
Turn Laravel errors and logs into GitHub issues (with deduplication, comments on repeats, tracing, and customizable Markdown templates). Implemented in src/Loggers/Github/.
Publish and configure:
Configure in config/essentials.php under loggers.github (publish with php artisan vendor:publish --tag=essentials-config if needed).
GITHUB_LOGGER_ENABLED=true GITHUB_LOGGER_REPO=your-org/your-repo GITHUB_LOGGER_TOKEN=ghp_...
When enabled, Essentials registers the github log channel automatically. Use it as LOG_CHANNEL, in a stack, or explicitly:
Log::channel('github')->error('Something went wrong!');
Optional: publish issue templates with php artisan vendor:publish --tag=essentials-loggers-github-views.
Roadmap
- Better defaults before each test case
- General cleanup of the skeleton
- Additional configurables for common Laravel patterns
Credits
This package is maintained by Ivan Führ.
It builds on ideas and code from these projects:
- nunomaduro/essentials — Nuno Maduro
- GitHub issues logger for Laravel — Krishan Koenig and contributors
Thank you to everyone who made the upstream work possible.
License
The MIT License (MIT). Please see LICENSE.md for more information.