jand3v/laravel-opinionated-essentials

Opinionated essentials for Laravel applications.

Maintainers

Package info

github.com/jand3v/laravel-opionated-essentials

pkg:composer/jand3v/laravel-opinionated-essentials

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.1 2026-06-04 20:44 UTC

This package is not auto-updated.

Last update: 2026-06-06 08:44:25 UTC


README

Opinionated essentials for Laravel applications. Applies sensible framework defaults automatically and provides generators for common patterns.

Installation

composer require jand3v/laravel-opinionated-essentials

The service provider is auto-discovered — no manual registration required.

Configuration

Publish the config file to adjust or disable individual defaults:

php artisan vendor:publish --tag=opinionated-essentials-config

This creates config/opinionated-essentials.php in your application. Every default can be toggled via environment variables.

Opinionated Defaults

The following behaviors are applied automatically when the package is installed:

Behavior Config key Env variable Default
Eloquent lazy-loading prevention (non-production) models.prevent_lazy_loading OPINIONATED_ESSENTIALS_PREVENT_LAZY_LOADING true
Carbon immutable dates dates.immutable OPINIONATED_ESSENTIALS_IMMUTABLE_DATES true
Secure password rules in production passwords.secure OPINIONATED_ESSENTIALS_SECURE_PASSWORDS true
Prohibit destructive DB commands in production database.prevent-destruction OPINIONATED_ESSENTIALS_PREVENT_DB_DESTRUCTION true

To disable a default, set the env variable to false or update config/opinionated-essentials.php.

Lazy Loading Prevention

Outside of production, accessing an unloaded Eloquent relationship throws a LazyLoadingViolationException, forcing eager loading and preventing N+1 queries:

// ✗ Throws LazyLoadingViolationException outside production
$post->comments;

// ✓ Always safe
Post::with('comments')->get();

Immutable Dates

All date instances returned by Eloquent are CarbonImmutable, preventing accidental mutation:

$user->created_at->addDay(); // returns a new instance, original unchanged

Secure Passwords (production only)

In production, Password::defaults() requires a minimum of 12 characters, mixed case, letters, numbers, symbols, and checks the password against the HaveIBeenPwned database:

// Use the default password rule in your form requests
'password' => ['required', Password::default()],

In non-production environments Password::default() returns null (no extra constraints), making registration easy during development.

Prohibit Destructive Commands (production only)

DB::prohibitDestructiveCommands(true) is called in production, preventing migrate:fresh, migrate:reset, and db:wipe from running accidentally.

Generators

make:action

Creates a new Action class in app/Actions:

php artisan make:action PlaceOrder

Generates app/Actions/PlaceOrderAction.php:

<?php

declare(strict_types=1);

namespace App\Actions;

final readonly class PlaceOrderAction
{
    public function __construct() {}

    public function execute(): void
    {
        //
    }
}

The Action suffix is added automatically. Passing PlaceOrderAction produces the same result — no double suffix.

--dto option

Pass --dto to generate an accompanying DTO and wire it into execute():

php artisan make:action PlaceOrder --dto

Generates both app/Actions/PlaceOrderAction.php and app/DTOs/PlaceOrderData.php:

// app/Actions/PlaceOrderAction.php
final readonly class PlaceOrderAction
{
    public function __construct() {}

    public function execute(PlaceOrderData $data): void
    {
        //
    }
}
// app/DTOs/PlaceOrderData.php
final readonly class PlaceOrderData
{
    use MapsFromArray;

    public function __construct(
        // Add your properties here
    ) {}
}

make:dto

Creates a standalone DTO in app/DTOs without an Action:

php artisan make:dto PlaceOrder

Generates app/DTOs/PlaceOrderData.php (same structure as above). The Data suffix is added automatically — passing PlaceOrderData produces the same result.

DTOs and MapsFromArray

Every generated DTO uses the MapsFromArray trait, which maps an array (e.g. from $request->validated()) to constructor properties.

Defining a DTO

use Jand3v\OpinionatedEssentials\Dto\Attributes\MapFrom;
use Jand3v\OpinionatedEssentials\Dto\Concerns\MapsFromArray;

final readonly class PlaceOrderData
{
    use MapsFromArray;

    public function __construct(
        public int $customerId,
        public string $customerName,       // mapped from 'customer_name'
        #[MapFrom('order_total')]
        public int $totalAmount,           // mapped from 'order_total'
        public ?string $note = null,       // optional, defaults to null
    ) {}
}

Creating instances

From a controller with a validated form request:

public function store(Request $request): Response
{
    $data = PlaceOrderData::from($request->validated());
    // $data->customerId, $data->customerName, $data->totalAmount
}

Or from any array:

$data = PlaceOrderData::from([
    'customer_id'  => 42,
    'customer_name' => 'Ada Lovelace',
    'order_total'  => 9900,
]);

Mapping resolution order

For each constructor parameter, the trait resolves the value in this order:

  1. #[MapFrom('key')] — explicit array key (highest priority).
  2. Exact camelCase name — e.g. customerName matches array key customerName.
  3. snake_case fallback — e.g. customerName also matches customer_name.
  4. Default value or nullable — uses the parameter's default, or null if nullable.
  5. InvalidArgumentException — thrown when a required key is missing.

Testing

composer install
vendor/bin/pest

License

The MIT License (MIT). See LICENSE for details.