feat: replace generic validation rules with field-type-owned validation capabilities#75
Conversation
Replace generic ValidationRule enum + repeater with field-type-owned validation capabilities on FieldSchema for v3.
16-task TDD plan covering foundation, capabilities, wiring, admin UI, field type updates, migration step, and cleanup.
…tion Add upgrade step that converts validation_rules from old array-of-objects format to the new key-value format. Maps rules based on field type: text -> min/max_length, numeric -> min/max_value, multi-select -> min/max_selections, date -> min/max_date with relative date support, file -> max_size_kb and accepted_types. Unmappable rules are discarded with warnings. Registered before clean-multivalue-rules step.
There was a problem hiding this comment.
Pull request overview
This PR replaces the generic ValidationRule enum-based validation system with a field-type-owned validation capabilities architecture. Each field type declares validation capabilities via FieldSchema fluent methods (e.g., ->canHaveMinDate(), ->canHaveMaxLength()). Each capability is a self-contained class implementing the ValidationCapability interface, owning its admin form UI, Filament component application, and Laravel rule generation. The system adds support for relative date constraints (e.g., "7 days from now") via DateConstraintValue with DateConstraintMode and DateUnit enums. The migration includes an MigrateValidationRulesFormatStep to convert from old [{name, parameters}] to new {key: value} format. The PR removes ~2,000 lines of legacy code including the ValidationRule enum, ValidationRuleData DTO, and CustomFieldValidationComponent repeater.
Changes:
- Introduces
ValidationCapabilityinterface with 8 implemented capabilities (text length, selection count, date constraints, file validation) - Replaces
validation_rulesDataCollection cast with AsCollection, changing storage from[{name, parameters}]to{key: value}format - Adds
DateConstraintValuevalue object supporting absolute and relative date constraints with newDateConstraintModeandDateUnitenums - Updates all 22 field type definitions to declare capabilities via FieldSchema instead of availableValidationRules
- Removes ValidationRule enum (100+ cases), ValidationRuleData DTO, CustomFieldValidationComponent repeater, and related infrastructure
Reviewed changes
Copilot reviewed 70 out of 70 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Contracts/ValidationCapability.php | New interface defining capability contract |
| src/Validation/Capabilities/*.php | 8 capability implementations (date, text, selection, file) |
| src/Enums/DateConstraintMode.php, DateUnit.php | New enums for relative date support |
| src/Data/DateConstraintValue.php | Value object for date constraint resolution |
| src/FieldTypeSystem/FieldSchema.php | Added capability registration methods |
| src/Models/CustomField.php | Changed validation_rules cast to AsCollection |
| src/Services/ValidationService.php | Simplified to iterate capabilities |
| src/Filament/Integration/Base/AbstractFormComponent.php | Applies capabilities to form components |
| src/Filament/Management/Schemas/FieldForm.php | Renders capability-based validation tab |
| src/Console/Commands/Upgrade/Steps/MigrateValidationRulesFormatStep.php | Migration from old to new format |
| tests/* | 336 tests covering capabilities, migration, integration |
| src/FieldTypeSystem/Definitions/*.php | All 22 field types updated to use capabilities |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/Filament/Integration/Support/Imports/ImportColumnConfigurator.php
Outdated
Show resolved
Hide resolved
- Move required toggle from validation tab into settings fieldset - Replace validation tab with inline validation fieldset (only visible when field type has capabilities) - Switch settings toggles to inline mode for compact layout - Replace helperText with hintIcon tooltips for cleaner UI - Use 3-column grid for settings fieldset - Remove unused Exception import
…pport - Remove DateConstraintMode enum (absolute mode) - Add DateDirection enum (ago/from_now) for intuitive date constraints - Add Quarters to DateUnit - Simplify DateConstraintField UI: value + unit + direction (3-column) - Move required toggle into validation fieldset - Move validation fieldset above options repeater - Remove unnecessary minLength/maxLength from email, phone, link types - Update migration step to warn on absolute dates instead of converting - Update all related tests
…apabilities - Remove `2026-02-17-validation-capabilities-design.md` - Remove `2026-02-17-validation-capabilities-implementation.md` - Documentation no longer reflects current architecture after recent refactors
- Use DateConstraintValue::from() for proper Spatie Data hydration - Move validation fieldset into its own tab before Visibility - Reimplement import validation using ValidationService - Add Copilot custom instructions for Filament v5 namespaces
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 72 out of 72 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Covers anchor-based data model (today/fixed/custom-field/record-created), preset-driven config UI with progressive disclosure, and runtime changes for submit-time validation and cross-field reactivity.
9-task TDD plan covering enums, DTO, validation rule, capabilities, config UI, reactive field refs, circular reference detection, and integration testing.
Add DateValidationManagementTest with 7 tests verifying persistence of date fields with various validation constraints (today anchor, fixed date, custom field reference, record created, offset, no restrictions). Add DateValidationIntegrationTest with 6 tests verifying runtime validation on CreatePost forms (reject past dates, accept valid dates, max date enforcement, fixed date constraint, offset validation). Fix ValidationService::combineRules() and DatabaseFieldConstraints to handle non-string validation rules (DateConstraintRule objects).
Use $record injection instead of $get() path traversal to read entity_type and code. The hidden entity_type field wasn't being properly hydrated in Filament 5's action form context, causing $get() to return null at any traversal depth. Falling back to $get() for the create-new-field path where $record is null.
Add defensive checks in date capabilities to handle pre-existing database records saved before sanitizeValidationRules was introduced. Extract AbstractDateCapability to centralize shared logic and guards.
Full round-trip E2E tests covering all anchor types for date and date-time fields. Regression tests for legacy data with preset:none but no anchor key.
Fix incorrect Component namespace imports, add phpstan-ignore for dynamic Field method dispatch, and simplify migration step code.
Remove 11 unit test files (85 tests) and 2 plan documents. Add ValidationCapabilitiesTest with 29 feature tests covering text, numeric, selection, required, and date edge cases through real Livewire form submission.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 76 out of 76 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
parseDateConstraint was outputting relative_value/relative_unit/direction keys which don't match DateConstraintValue DTO that expects anchor/offset/offset_unit/offset_direction. Updated to produce the correct format.
Replace direct CustomField::query() and new CustomField/CustomFieldValue with CustomFields::newCustomFieldModel() and CustomFields::newValueModel() to respect configurable model overrides. Add architecture test with datasets to enforce this pattern across all 4 configurable models.
Remove IntegerOnlyCapability from NumberFieldType and use DecimalPlacesCapability instead (decimal_places=0 for integers). Limit decimal places to 0-4 with placeholder hint. Update migration step to map legacy 'integer' rule to decimal_places=0.
Apply Rector auto-fixes (FQCN imports, sprintf conversions, newlines). Remove stale phpstan ignore patterns no longer needed after Rector fixes.
The argument.type errors in Infolists and Table Columns are caused by Filament's view-string type declarations varying across versions. Use reportUnmatched: false so it works on both local and CI environments.
| ->withValidationCapabilities( | ||
| MinValueCapability::class, | ||
| MaxValueCapability::class, | ||
| DecimalPlacesCapability::class, |
There was a problem hiding this comment.
NumberFieldType uses FieldSchema::numeric() (stored via FieldDataType::NUMERIC → integer_value and SafeValueConverter::toSafeInteger()), but enables DecimalPlacesCapability. When decimal_places > 0, the generated decimal:* rule can allow decimals that will then be truncated when persisted, causing silent data loss. Either restrict this capability to float-backed field types, or change the number field’s data type/storage to float when decimals are supported.
| DecimalPlacesCapability::class, |
| public function getValidationRules(CustomField $customField, string|int|null $ignoreEntityId = null): array | ||
| { | ||
| // Convert user rules to Laravel validator format | ||
| $userRules = $this->convertUserRulesToValidatorFormat($customField->validation_rules, $customField); | ||
| // Get capability-based rules from stored values | ||
| $capabilityRules = $this->getCapabilityRules($customField); | ||
|
|
There was a problem hiding this comment.
getValidationRules() no longer adds the field-level required constraint to the returned rule list. This is fine for Filament forms that also call ->required(...), but callers that only use getValidationRules() (e.g. import column rules) will stop enforcing required fields. Consider prepending 'required' when $this->isRequired($customField) is true (or providing a dedicated method/flag for non-Filament contexts).
Summary
ValidationRuleenum + repeater UI with a validation capabilities system where each field type declares its own validation capabilities viaFieldSchemaValidationCapabilityinterface) owns its admin form schema, Filament component application, and Laravel rule generation — eliminating the gap between what admins configure and what actually validatesDateConstraintValuevalue object withDateConstraintModeandDateUnitenumsMigrateValidationRulesFormatStepupgrade step to convert old[{name, parameters}]format to new{key: value}formatValidationRuleenum,ValidationRuleDataDTO,CustomFieldValidationComponentrepeater,ValidationRulesDatasetCapabilities Added
MinLengthCapability,MaxLengthCapabilityMinValueCapability,MaxValueCapability,IntegerOnlyCapability,DecimalPlacesCapabilityMinSelectionsCapability,MaxSelectionsCapabilityMinDateCapability,MaxDateCapability(with absolute + relative mode)MaxFileSizeCapability,AcceptedFileTypesCapabilityTest Plan
custom-fields:upgradeon DB with old-format validation rules