padosoft/laravel-validable
A Laravel package that adds model-level validation via a Validable trait. Define rules and messages directly on Eloquent models and validate automatically before save or update, keeping persistence logic cleaner and data safer.
Architecture fit. laravel-validable is a single-trait package that hooks validation into the Eloquent saving model event, letting a model declare $rules, $messages, and attribute names in one place. For a TPM, the appeal is genuine: it collapses duplicated create/update validation from controllers and FormRequests into the model layer and guarantees a validator runs on every save(). The concern is architectural direction: Laravel's conventions push validation toward the HTTP boundary (FormRequests) or toward DTOs/value objects (e.g. spatie/laravel-data), not into persistence. Embedding rule definitions in Eloquent models couples a domain object to a validation library and to framework lifecycle events, and it makes "save succeeded" mean something different from what most engineers on the team expect. It is a reasonable fit for small, CRUD-heavy applications with a handful of models; it is a poor fit for a codebase that already has a consistent FormRequest or DTO validation layer, or one with heavy bulk-write, import, or queue-driven persistence paths.
Integration feasibility. Integration itself is cheap: composer require, add use Validable; to a model, define rules, done. There is no config file or service provider surface to speak of. The real feasibility question is not install cost but escape-hatch coverage — whether the trait provides documented ways to skip validation for seeders, factories, bulk imports, firstOrCreate/upsert, and data-repair scripts. Because the trait fires on the Eloquent event, query-builder writes (Model::insert(), ->update() on a builder) bypass it entirely, so the "auto-validate everything" promise is narrower than it sounds; the team must understand exactly which write paths are covered. This asymmetry is the single biggest source of surprise in adoption.
Technical risk. Rated against a 7-star, 0-dependent, last-release-January-2023 package, risk is elevated and mostly non-technical:
| Risk | Severity | Notes |
|---|---|---|
| Laravel version drift | High | Last release predates Laravel 11/12; composer constraints likely need verification or a fork to run on current Laravel |
| Bus factor / maintainer | High | Single-vendor (Padosoft) package, no visible community, no security disclosure process |
| Implicit behavior | Medium | Validation failures surface as exceptions from save(); teams accustomed to returning $validator->fails() arrays will mis-handle them |
| Unique/complex rules | Medium | unique rules inside a save trigger extra queries and require Rule::unique()->ignore($this->id) handling for updates — a classic correctness trap |
| Transaction interaction | Medium | Validation fires mid-transaction; on long transactions this extends lock duration and can abort partially-applied units of work |
| Testing friction | Medium | Factories and seeders begin failing on incomplete fixtures; test suites need auditing |
Key questions to answer before committing:
unique ignore semantics?Illuminate\Contracts\Validation\Validator-style error bags that our exception handler already renders?saveQuietly, a withoutValidation() scope, a static toggle)?Stack fit. Low-friction technically, moderate-friction culturally. It requires no infrastructure, no queue worker, no config, and no new runtime dependency beyond the package itself. It fits naturally in a Laravel app that already relies on Eloquent events and observers for cross-cutting persistence concerns (slugs, auditing, UUIDs). It clashes where validation is already centralized in FormRequests or where the team treats models as anemic.
Migration path. Do not roll this out broadly. Pick one low-risk, high-churn model — ideally a settings or profile-style aggregate with simple rules and no bulk-write path — and pilot it in a feature branch. Simultaneously, build the guardrails you will need at scale: an abstract base model (or a ValidatesOnSave interface) so the trait is applied consistently rather than ad hoc; an withoutValidation() convention documented in the README; and an exception-handler mapping so failures produce clean 422s in HTTP contexts and non-retryable-job behavior in queues.
Compatibility. Pin the version explicitly in composer.json and verify the constraint against our Laravel and PHP matrix before the pilot. If the published release predates our framework version, decide up front between (a) a Composer patch/fork under our org namespace, or (b) abandoning the package — do not let the team discover this in week three. Also confirm interaction with soft deletes, upsert, firstOrCreate, and model events ordering relative to observers.
Sequencing.
Maintenance. The package is a thin trait, so its intrinsic maintenance burden is low — but the ownership burden is not. With one maintainer and a dormant release cadence, plan on internal ownership: a fork in our VCS, a documented upgrade procedure, and a named owner. Budget a few hours per Laravel major upgrade to re-verify the trait against model event changes.
Support. There is effectively no vendor support and a negligible community (single-digit stars, zero dependents). Any issue we hit, we fix. That is acceptable for a 40-line trait and unacceptable for anything load-bearing across dozens of models unless we fork.
Scaling. Runtime overhead is one validator instantiation and rule evaluation per save — negligible. The measurable cost is unique and other DB-backed rules issuing extra queries on every write, which matters in high-throughput write paths and inside long transactions. Bulk operations bypass the trait entirely, so "validated" is a property of Eloquent saves only, not of the database. That asymmetry must be documented or it becomes an implicit data-quality gap at scale.
Failure modes.
save() in code paths that assumed a boolean return — most commonly in queue jobs, seeders, factories, and admin data-repair scripts.insert, or upsert, producing unvalidated rows that the team believes are validated.ignore() is omitted.Ramp-up. The API is small enough that a mid-level engineer is productive in under an hour. The cost is not learning the trait — it is learning the boundaries: which writes validate, how to opt out, what exception to expect, and where the team's existing FormRequests now overlap. That boundary documentation, not the package, is the actual onboarding artifact. Treat the package as an internal convention with a small third-party core, and the operational risk becomes manageable.
How can I help you explore Laravel packages today?