marko/validation
A PHP validation package for Laravel-style applications, offering reusable rules and validators to keep request data clean and consistent. Helps centralize validation logic so forms, APIs, and models share the same checks.
This is a Marko Framework component, not a Laravel package. The repo is a read-only split of the marko-php/marko monorepo, so treat it as a source-reading exercise first and an install second.
github.com/marko-php/marko. The split repo here will not accept PRs and may lag the monorepo. Before you write a line of code, open the monorepo's src/marko/validation (or equivalent package path) and read Factory, the base Validator, and the Rule interface — those three files define the whole contract.composer require marko/validation only makes sense if you're bootstrapping Marko's container and service providers. In a Laravel app you'd be pulling in Marko's container assumptions for very little gain — see Gotchas.fails()/passes() and read the error bag. That single flow is 90% of what you'll ever do with it.Mental model for a Laravel dev: it is conceptually a sibling of Illuminate\Validation — rule strings, a factory that resolves rule objects, an error message bag — but the names, container wiring, and message formatting are Marko's, not Laravel's. Do not expect $request->validate() or Validator::make() to exist with identical signatures.
Rule-string validation (the common path). Pass an associative array of field => 'rule|rule|rule' alongside the data. This is the pattern you'll use for 80% of endpoints and it will feel familiar if you've written Laravel rules.
Rule objects for anything non-trivial. When a rule needs dependencies (a repository lookup, an HTTP client, a config value), define a class implementing the package's Rule contract and resolve it through the container. This is the cleanest integration point and the one to standardize on for a team — string rules for primitives, rule objects for domain logic (unique email, valid SKU, tenant-scoped existence).
Wrap it in your own request layer. Marko doesn't give you Laravel's FormRequest. Build a thin base class or middleware in your app that: pulls input, runs the validator, and either returns a 422 with the message bag or hands a validated array to the controller. Keep that adapter in your code, not scattered through controllers — it's the seam you'll swap if you ever migrate off Marko.
Nested and array data. Use dotted/wildcard paths (items.*.sku) for collections. Verify wildcard expansion actually works against your installed version before relying on it — it's the most version-sensitive behavior in most validator implementations.
Reusable rule sets. Store rule arrays as class constants or config entries keyed by scenario (store, update), and compose them (array_merge plus an sometimes-style rule for partial updates). This keeps update-vs-create divergence explicit instead of duplicated.
Testing. Because there's no HTTP layer baked in, validators are trivially unit-testable: construct with an array, assert fails() and inspect the error keys. Write one test per rule object; skip integration tests for string rules unless you've customized messages.
If you need cross-framework rules. Keep domain rules as plain PHP classes with no Marko base class, and add a one-line adapter implementing the Rule contract. Then the same rule works under Laravel's Illuminate\Validation later. This is the single highest-leverage decision if there's any chance the app isn't staying on Marko.
composer.json to a tagged version from the monorepo's release train, not a branch of the split.Illuminate\Validation interop, no $request->validate(), no Rule::unique() builder, different message-bag shape and different translation file layout. If you're in a Laravel app, illuminate/validation is almost certainly the right answer and this package is not.new them directly. The latter is worth the small extra effort.errors()->all() / errors()->first('field') semantics match Laravel's. Dump the raw error structure once, early, and write your API error formatter against what you actually see.validation.required style keys) rather than throwing. If messages look wrong, check the translation namespace is registered in the Marko bootstrap before you touch the rule.required-adjacent rules depending on config), confirm the rule string is spelled as the package expects (snake_case vs camelCase matters), and confirm you're not validating a copy of the data instead of the request payload.Rule classes and a custom message formatter. Avoid monkey-patching the factory or overriding the base validator — that's the code most likely to break on a monorepo release, with no changelog visibility from this repo.illuminate/validation is an afternoon, not a rewrite.How can I help you explore Laravel packages today?