PHP Conditional Validation Without the Mess
Every PHP developer has written this at some point:
if ($data['account_type'] === 'busine...
For further actions, you may consider blocking this person and/or reporting abuse
Have you tried the Symfony validator component ?
That library can handle conditional validations.
If that matters would your library be a no-go as well?
The one thing where I see a footgun, from the examples in the post, is setting the schema/rules and then using it. What if the schema or rule is changed somewhere else in the code?
Having a hard-coded/config validation schema is safer in my book.
Symfony Validator - Handles conditionals, but most PHP validation libs do.
The usual approaches are either callbacks or, in Symfony's case, inline expression
strings where typos or syntax errors can be hard to catch. I wanted method chaining
with IDE support - harder to mess up, follows standard conditional patterns:
Not revolutionary, just trying to keep it lightweight and readable.
"Zero dependencies" - Bad wording on my part. Should've said "no transitive
dependencies". Avoiding npm-style trees where one lib pulls 45 packages who depends
either on N other packages. Easier audits, less supply chain risk. I'll update the post.
Schema mutability - Duplicate registration throws
LogicException.Pattern is documented: register once at bootstrap, consume everywhere. Tested
this specifically with FrankenPHP worker mode (3M+ requests) - no issues.
Thanks for the feedback.
On the Symfony validator, expressions are not meant to be complex so syntax errors are not that hard to catch. For the typos, I don't think your library is in a better position to to handle them.
Also in the expression the properties are used instead of input keys, which makes the the validation less prone to input key changes.
It would bug me to repeat the field and account_type check over and over.
With the Symfony validator it is possible to add multiple
Whenconstraints as a part of theWhenfor the account_type check.It is more verbose but for me it is easier to follow.
What I'm also missing is custom messages.
To be clear, I like the library. I just playing the devil's advocate
I regularly use the devil's advocate approach myself, so appreciate it :)
Expression complexity - True for simple cases. But when you chain multiple
conditions (
type == "business" and country == "FR" and vat_required == true),both approaches have the same readability/error risk. Just different syntax.
Properties vs keys - That's the trade-off of not validating DTOs/Entities yet.
'this.getType()'in an ExpressionLanguage string has the same refactoring risk as'account_type'in method arguments - no IDE autocomplete, no compile-time checks,runtime errors only. Same problem, different syntax.
Repetition - If you have multiple fields depending on
account_type = business,yeah it gets verbose. But that's how you'd write it in standard conditionals too
(if/else, early returns). For reusable patterns, schemas and rules handle this.
Custom messages - Messages are currently field-level, not condition-level. You
can create custom rules with hardcoded messages (no i18n yet), but per-condition
messages without bloating the API - still figuring out the cleanest approach.
Good to know you see value in it. This kind of feedback helps understand where it
fits vs where it doesn't.
I was more thinking about following code.
As you see the expressions are very short, there is no repetition and the flow is easy to follow.
Fair point on the nested conditionals - that's a case where Symfony's approach is structurally cleaner.
Currently, I'd need to repeat the
category = businesscondition per country(verbose but explicit), or hide it in a custom validation strategy (loses the
declarative flow).
Line-wise though, even with repetition:
~15 lines vs 32 - more compact despite the repetition. Obviously, the more you try to nest elements, the more repetitive it will become.
One approach I'm considering: conditional validation groups with inheritance.
Execution order: Parent condition first, then groups (each inheriting parent
validations). Group names as i18n keys for custom messages, aliases for field names.
Example flow (category=business, country=US, vat empty):
category = business✓country = US✓requiredfailsExample flow (category=business, country=US, vat="ABC123"):
requiredpassesregex('/^\d{9}$/')failsStill exploring the cleanest implementation - adds complexity but keeps the
pattern declarative.
Appreciate the concrete example.