michael-rubel/laravel-helpful-traits
A collection of reusable traits for Laravel that cuts boilerplate across models, controllers, form requests, and more. Drop-in traits add common behaviors like UUIDs, slugs, query scopes, caching, and response helpers, keeping your code concise, consistent, and aligned with Laravel conventions.
michael-rubel/laravel-safe-request is a small trait package (the GitHub repo name is the authoritative one — don't go looking for a helpful-traits package). The capability it provides changes a FormRequest so that property and array access ($request->key, $request['key']) resolve from $request->safe() instead of $request->all(). The practical effect: the magic accessor becomes a "validated-only" accessor, so unvalidated input cannot be read by accident through the most common syntax.
Minimal setup:
composer require michael-rubel/laravel-safe-request
There is no service provider or config file to expect from a trait package. Read the README first for the exact trait FQCN, then read src/ (small) and tests/ (small) to see the precise accessor overrides. Confirm the package's composer.json constraint against your Laravel version — 2.0.1 explicitly adds Laravel 10 support, and the constraint should now permit illuminate/* ^10.
Apply it to a form request:
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use MichaelRubel\SafeRequest\...; // confirm exact name in README
class StorePostRequest extends FormRequest
{
use SafeRequest; // package trait
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255'],
'body' => ['required', 'string'],
];
}
}
Then in the controller:
public function store(StorePostRequest $request)
{
$title = $request->title; // backed by safe()
$post = Post::create($request->safe()->all());
}
First use case: a controller that currently does Post::create($request->all()) or scatters $request->foo lookups everywhere. Swap in the trait, then grep for property reads whose keys have no rule — those are exactly the places you were consuming unvalidated input.
Apply once via a base request. The highest-leverage pattern is a single abstract base class that every request extends, so the trait is inherited everywhere and cannot be forgotten:
abstract class BaseFormRequest extends FormRequest
{
use SafeRequest; // package trait
}
Bulk extraction through safe(). Prefer the ValidatedInput helpers over constructing arrays by hand:
Post::create($request->safe()->all());
Post::create($request->safe()->only(['title', 'body'])->all());
Post::create($request->safe()->except(['_token'])->all());
Keep an explicit escape hatch for raw input. The trait only changes magic access; input(), query(), all(), and has() still hit the raw request. Use them deliberately, not by accident:
$utm = $request->input('utm_source'); // raw, unvalidated
Nested data. Magic property access does not do dot notation. Use $request->safe()->all() plus data_get():
$city = data_get($request->safe()->all(), 'address.city');
Service/action handoff. Pass $request->safe()->all() or a DTO built from it into your domain layer. This keeps the "unvalidated input never leaves the HTTP layer" boundary intact.
Testing. Nothing changes from a test perspective — $this->post('/posts', $payload)->assertRedirect() still works, because the request is validated before the controller runs.
Compare, don't guess. During a migration, log both sides to build an inventory:
logger()->debug('safe', $request->safe()->all());
logger()->debug('validated', $request->validated());
laravel/framework ^10 / illuminate/* ^10 in the constraint). No API, trait-name, or behaviour changes, so bumping from 2.0.0 requires no code edits — just confirm your composer.json allows the new constraint and run composer update michael-rubel/laravel-safe-request.__get() and offsetGet(). If you want zero supply-chain surface, vendor that logic into your own BaseFormRequest — you get the same benefit with full control.$request->input('x'), $request->all(), $request->query(), $request->only(), and $request->has() still return raw input. The trait is a nudge, not a security boundary. Enforce it with code review or a static-analysis rule that bans $request->all() in controllers.authorize() and prepareForValidation() run before the validator is populated, so safe() is not usable there. Use $this->input() and $this->route() inside those hooks.Request::__get falls back to $this->route($key), which is how $request->post returns a route-model binding. If the trait replaces __get wholesale without that fallback, route-parameter property access can silently return null. Verify with a quick test, or override __get yourself as safe() → parent::__get().null rather than the raw value. That is the point, but it can mask bugs during the transition. Audit controller reads against your rules() arrays.merge() does not update safe(). The validator snapshots input at validation time; values merged afterwards are not reflected in safe() (or validated()).safe() is Laravel 9+. It returns a ValidatedInput object, not an array — call ->all() before passing it to Model::create() or an action. validated() remains the plain-array alternative, and for most teams $request->validated() without this package is the lower-friction default.safe(), and UploadedFile instances come through when the key has rules. $request->file('x') is still raw and unaffected.$request->title. If type safety matters more than brevity, prefer a DTO built from $request->validated().How can I help you explore Laravel packages today?