| description | Validate FHIR resources against the specification and Implementation Guides. |
|---|---|
| icon | shield-check |
The Validation component checks FHIR PHP model objects against constraints encoded as PHP 8 attributes on the generated model classes: structural rules, FHIRPath invariants, terminology bindings, profiles, extension contexts, ranges, and obligations. It supports R4, R4B, and R5, and produces a structured validation report.
- Structural & Profile Validation — cardinality, slices, fixed and pattern values.
- FHIRPath Invariant Validation — constraint expressions such as
obs-7. - Terminology & Binding Validation — codes against ValueSet bindings.
- Reference & Target Profile Validation — what a
Referencemay point to. - Quantity & Temporal Range Validation —
minValue/maxValuebounds. - Extensions, Modifiers & Obligations — context, modifier, must-support, and obligation checks.
- Questionnaire Validation — a separate validator for questionnaires and responses.
- The $validate Operation — producing an
OperationOutcome. - Configuration — wiring terminology clients, resolvers, and obligation contexts.
- Validation Reports & Violation Codes — report structure and severity codes.
use Ardenexal\FHIRTools\Component\Validation\FHIRValidationService;
use Ardenexal\FHIRTools\Component\Models\R4\Resource\PatientResource;
// $validator is a Symfony ValidatorInterface wired with the FHIR constraint validators.
// In Symfony DI this is handled by FHIRBundle; for manual wiring see Configuration.
$service = new FHIRValidationService($validator, $pathService);
$report = $service->validate(new PatientResource());
if ($report->isValid()) {
echo "Patient is valid\n";
} else {
foreach ($report->errors() as $violation) {
printf("[%s] %s: %s\n", $violation->severity, $violation->path, $violation->message);
}
}{% hint style="info" %}
Manual wiring of the constraint-validator factory, terminology client, reference resolver, and
type-hierarchy resolver is covered on the Configuration page. Symfony DI
users get this automatically via FHIRBundle.
{% endhint %}
FHIRValidationService::validate() has the following signature:
public function validate(
object $resource,
array $profileUrls = [],
bool $includeMustSupportInfo = false,
?FHIRObligationContext $obligationContext = null,
): FHIRValidationReportValidation is attribute-driven. FHIR constraints are emitted as PHP 8 attributes on the
generated model classes during code generation (fhir:generate). At runtime,
FHIRValidationService calls Symfony Validator's validate() — which reads the attributes and
dispatches each to its matching ConstraintValidator — then augments the result with
FHIR-specific checks that Symfony cannot express (extension contexts, modifier extensions,
must-support, obligations).
| Attribute | Validator | FHIR concept enforced | Page |
|---|---|---|---|
#[FHIRPathInvariant] |
FHIRPathInvariantValidator |
FHIRPath constraint expressions | Invariants |
#[FHIRValueSetBinding] |
FHIRValueSetBindingValidator |
Terminology bindings | Terminology |
#[FHIRProfileConstraint] |
FHIRProfileConstraintValidator |
Profile cardinality/value rules | Structural |
#[FHIRFixedValue] |
FHIRFixedValueValidator |
fixed[x] element constraints |
Structural |
#[FHIRPatternValue] |
FHIRPatternValueValidator |
pattern[x] element constraints |
Structural |
#[FHIRSliceConstraint] |
FHIRSliceConstraintValidator |
Slice cardinality | Structural |
#[FHIRTargetProfile] |
FHIRTargetProfileValidator |
Reference target profiles | References |
#[FHIRQuantityRange] |
FHIRQuantityRangeValidator |
minValue/maxValue on Quantity |
Ranges |
#[FHIRTemporalRange] |
FHIRTemporalRangeValidator |
minValue/maxValue on date/time |
Ranges |
| Check | Method | What it enforces |
|---|---|---|
| Must-support collection | collectMustSupportInfo() |
Null/empty must-support properties → INFO (opt-in) |
| Extension context | validateExtensionContexts() |
Extension applied outside its declared context → ERROR |
Extension contextInvariant |
validateExtensionContexts() |
FHIRPath invariant on the extension's use context |
| Modifier extension walk | validateModifierExtensions() |
Unknown modifier extension URLs → ERROR (needs registry) |
| Obligation enforcement | collectObligationViolations() |
Actor-scoped populate obligations → ERROR/WARNING/INFO |
These are documented under Extensions, Modifiers & Obligations.
FHIRValidationService::validate($resource, $profileUrls, $includeMustSupportInfo, $obligationContext)
│
├─ Symfony Validator::validate($resource, groups=['Default', ...$profileUrls])
│ └─ reads #[FHIRPathInvariant], #[FHIRValueSetBinding], #[FHIRProfileConstraint],
│ #[FHIRFixedValue], #[FHIRPatternValue], #[FHIRSliceConstraint],
│ #[FHIRTargetProfile], #[FHIRQuantityRange], #[FHIRTemporalRange],
│ and built-in Symfony constraints (#[NotBlank], #[Count], etc.)
│
├─ collectMustSupportInfo() (only when $includeMustSupportInfo = true)
├─ validateExtensionContexts() (always; recursive walk across the full resource tree)
├─ validateModifierExtensions() (only when a FHIRIGTypeRegistry is provided)
└─ collectObligationViolations() (only when a FHIRObligationContext is provided)
└─ applyNoErrorSuppression() (suppress errors for SHALL:no-error obligations)
The FHIR validation spec (R4 / R5) defines several mandatory validation categories. Coverage as of the current release (applies to R4, R4B, and R5 unless noted):
| Validation category | Status | Notes |
|---|---|---|
| Cardinality (min/max) | Supported | Generated Symfony #[NotBlank] / #[Count] attributes |
| Required bindings | Supported | FHIRValueSetBindingValidator; enum-backed |
| FHIRPath invariants | Supported | Engine eval errors emit fhir:eval-error INFO (not ERROR) |
| Fixed / pattern values | Supported | FHIRFixedValueValidator / FHIRPatternValueValidator |
| Slicing (closed/open/openAtEnd) | Supported | FHIRSliceConstraintValidator |
| Profile constraints (generated) | Supported | FHIRProfileConstraintValidator; requires pre-generated models |
| Profile constraints (dynamic/runtime) | Not supported | Dynamic StructureDefinition loading not yet supported |
| Extension contexts (element/fhirpath/extension) | Supported | Recursive walk; defer-not-deny safety; see Extensions |
| Modifier extensions (unknown URL) | Supported | Recursive walk; requires a FHIRIGTypeRegistry |
| Extensible / preferred bindings | Conditional | Needs a real terminology client; otherwise fhir:unchecked-binding INFO |
| Target profile references | Conditional | Needs a FHIRReferenceResolverInterface; null resolver skips silently |
| Quantity range | Supported | FHIRQuantityRangeValidator |
| Temporal range | Supported | FHIRTemporalRangeValidator; handles partial dates (YYYY, YYYY-MM) |
| MustSupport | Supported | Opt-in via $includeMustSupportInfo = true; INFO violations |
| Obligations (populate) | Conditional | SHALL/SHOULD:populate enforced; filter evaluation deferred |
$validate operation output |
Supported | validateForOperation() → OperationOutcomeResource |
| Questionnaire validation | Separate service | FHIRQuestionnaireValidator; see Questionnaire |
| Narrative / XHTML | Not implemented | — |
- FHIRPath evaluation errors produce an
fhir:eval-errorINFO violation, never a false ERROR. A valid resource is never failed by an unsupported FHIRPath expression. - Extensible / preferred bindings are only checked when a real
FHIRTerminologyClientInterfaceis wired. Without one, each skipped check emits afhir:unchecked-bindingINFO violation (queryable viaFHIRValidationReport::hasUncheckedBindings()). See Terminology. - Profile validation requires pre-generated models. Runtime/dynamic StructureDefinitions cannot be validated. All HL7 core profiles for R4/R4B/R5 are pre-generated.
- Target profile reference validation requires a
FHIRReferenceResolverInterface. The defaultNullFHIRReferenceResolverreturnsnull, silently skipping the check. See References. - Obligation filter evaluation is deferred: obligations with a non-null FHIRPath
filterare skipped; only unconditional obligations fire. #[FHIRIsModifier]marks modifier properties for introspection only — no active enforcement; consumers must check modifier element values themselves.- Questionnaire validation is a separate
FHIRQuestionnaireValidatorservice, not part ofFHIRValidationService. See Questionnaire.