Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Sanitizer Laravel Package

waavi/sanitizer

Waavi Sanitizer provides a simple, flexible way to clean and transform user input in Laravel before validation. Define reusable sanitizers with built-in filters like trim, escape, cast, and custom filters to keep your data safe and consistent.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and verify compatibility first

composer require waavi/sanitizer

Before writing any code, check the resolved version against your PHP / Laravel constraint. This package targets Laravel 7 and PHP 7.x. On Laravel 8+/PHP 8.1+ it may install but can throw deprecation notices or break in edge cases. If composer require pulls a very old tagged release, pin it explicitly and add a note in your upgrade checklist — the package is effectively unmaintained, so treat it as a vendored helper rather than a long-term dependency.

Where to look first

  1. src/Filters/ — the actual filter implementations are small classes implementing Waavi\Sanitizer\Contracts\Filter. Reading two or three of them tells you everything about the apply($value, $options) contract.
  2. config/sanitizer.php — custom filter registration and any global settings.
  3. The README's SanitizedRequest section — that's the only Laravel-specific integration surface.

Minimal standalone use

use Waavi\Sanitizer\Sanitizer;

$data = [
    'name'  => '  Jane Doe  ',
    'email' => '[email protected]',
    'age'   => '042',
];

$filters = [
    'name'  => 'trim|escape',
    'email' => 'trim|lowercase',
    'age'   => 'cast:integer',
];

$clean = (new Sanitizer())->sanitize($data, $filters);
// ['name' => 'Jane Doe', 'email' => '[email protected]', 'age' => 42]

Minimal form request use

use Waavi\Sanitizer\Laravel\SanitizedRequest;

class StoreUserRequest extends SanitizedRequest
{
    protected $filters = [
        'name'  => 'trim|escape',
        'email' => 'trim|lowercase',
        'phone' => 'digit',
    ];

    public function rules(): array
    {
        return [
            'name'  => 'required|string|max:120',
            'email' => 'required|email|unique:users,email',
            'phone' => 'nullable|string|min:7',
        ];
    }
}

First real use case: normalizing an email/username before a unique rule runs. This is the classic win — " [email protected] " should be validated as [email protected], otherwise uniqueness and format checks behave inconsistently against stored data.

Implementation Patterns

Prefer the container-bound sanitizer in services/jobs, not just form requests

The Sanitizer class is framework-agnostic. Resolve it once and pass filter maps around:

$sanitizer = app(Sanitizer::class);
$clean = $sanitizer->sanitize($payload, $this->filtersFor('import'));

Use this for CSV imports, queue payloads, console commands, and webhook handlers — anywhere you don't have a FormRequest. Form requests only cover HTTP; most real sanitation leaks past them.

Chain filters deliberately — order is the semantics

'trim|lowercase|escape' and 'escape|trim|lowercase' produce different results. Establish a convention per field type:

  • Free-text: trim|strip_tags (escape at render/Blade time, not here).
  • Identifiers/codes: trim|uppercase or trim|lowercase.
  • Digits-only fields (phone, VAT, postal): digit.
  • Numbers from form input: cast:integer / cast:float after trim.

Centralize filter maps for reuse

Form requests tend to duplicate the same $filters across Store* and Update*. Extract:

abstract class SanitizedBaseRequest extends SanitizedRequest
{
    protected function userFilters(): array { /* ... */ }
}

or keep a config/sanitizer-fields.php mapping logical entities to filter arrays, then merge in each request. This keeps sanitization consistent between API and web controllers.

Sanitize nested arrays with care

Nested payloads (e.g. items.0.qty) need nested filter arrays or wildcard keys depending on the version:

protected $filters = [
    'items' => [
        'qty'  => 'trim|cast:integer',
        'sku'  => 'trim|uppercase',
    ],
];

Wildcard syntax ('items.*.sku' => '...') has varied across releases — verify against the installed Sanitizer source before relying on it, and cover it with a test.

Custom filters are the extension point you'll actually use

namespace App\Sanitizers;

use Waavi\Sanitizer\Contracts\Filter;

class E164Phone implements Filter
{
    public function apply($value, $options = [])
    {
        if ($value === null || $value === '') {
            return $value;
        }
        return preg_replace('/[^\d+]/', '', (string) $value);
    }
}

Register in config/sanitizer.php (typically under custom_filters) or via the sanitizer's extend/registration API, then reference as 'phone' => 'trim|e164_phone'. Keep filters pure, single-purpose, and null-safe — they run on raw user input and will occasionally receive null, arrays, or unexpected scalars.

Pair it with validation, don't replace it

Sanitization is a pre-validation transform, not a validation rule. The flow should be:

  1. Sanitize (trim, cast, normalize case).
  2. Validate the sanitized data (rules(), unique, exists).
  3. Persist $request->validated() — which now contains sanitized values.

If a unique check depends on normalization, put that normalization in the sanitizer, not in the rule's callback — otherwise other entry points (imports, API) bypass it.

Testing pattern

Test filters as units ((new E164Phone())->apply(' +1 (555) 010-9999 ')) and test one representative request end-to-end by instantiating the FormRequest and asserting the sanitized payload. Do not rely on feature tests alone; filter-order bugs are best caught in isolation.

Gotchas and Tips

  • Sanitize-before-validate changes required semantics. Input of " " trimmed to "" will fail required, which is usually what you want — but it also means nullable+trim combos can produce "" instead of null. If your DB columns are nullable, add a ConvertEmptyStringsToNull-style step at the end of the chain.
  • Do not store escaped HTML. The escape filter will cause double-escaping when Blade renders it. Prefer strip_tags on input, escape on output.
  • Never sanitize passwords, tokens, or hashed values. trim on a password silently breaks users; lowercase on a token invalidates it. Whitelist the fields you filter rather than blanket-applying filters.
  • cast is not validation. cast:integer on "12abc" may yield 12, and on "abc" may yield 0 — which then passes integer validation. Combine with numeric/integer rules and consider strict custom casting when data integrity matters.
  • Filter strings are parsed by | and :. Values containing : inside options are ambiguous; avoid options with colons, and don't pass user-controlled filter strings into sanitize().
  • The $filters property must be protected. A public or missing property silently yields no filtering in some versions — a very common "it does nothing" bug.
  • Watch for double sanitation. Laravel's global TrimStrings and ConvertEmptyStringsToNull middleware already trim everything. Stacking the package's trim on top is harmless but hides which layer did the work; if a field is unexpectedly empty, check the middleware before blaming the sanitizer.
  • Debugging: dump the sanitized payload. The form-request path doesn't expose the transformed data easily. Temporarily override the sanitize call in a base request or log $sanitizer->sanitize($request->all(), $this->filters) to see exactly what validation receives.
  • Verify the all() / validationData() override. SanitizedRequest hooks the request lifecycle; confirm which method it overrides in your version (behavior has shifted between Laravel majors). If authorize() or custom withValidator code reads $this->all() before sanitization, it will see raw input.
  • API requests are not exempt. expectsJson() only changes error responses; the sanitizer still runs. If you have strict JSON contracts (e.g. client sends age as string but expects string back), cast will change the type in validated() output — document that.
  • Abandonment risk. With zero dependents and a Laravel 7 ceiling, plan a migration path. Native equivalents are FormRequest::prepareForValidation() + TrimStrings middleware + explicit casts in models or DTOs. The custom-filter classes port over directly.
  • Extension points to keep in mind: the Filter contract for reusable rules, config/sanitizer.php for registration, and the Sanitizer instance itself if you need to wrap it in your own service with default filters per context. Avoid subclassing SanitizedRequest deeply — the parent's internals are the least stable part of the package.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings