Skip to content

Validation

Daniel Carbone edited this page Mar 4, 2025 · 3 revisions

Each type generated by php-fhir contains a method called _getValidationErrors. This method recurses through all set fields on the type, running any / all defined validation rules for each field.

This library generates a number of validator rules, but they are limited in scope to only the rules defined by the FHIR schema itself. See Generated Validators for a list.

Executing Validation Rules

Lets take a simple example with the R4 string-primitive type

As you can see, the default validation rule on this type requires that, if defined, its value must have a minimum length of 1:

<?php

class FHIRStringPrimitive implements PrimitiveTypeInterface
{
    /* ... */

    private const _FHIR_VALIDATION_RULES = [
        self::FIELD_VALUE => [
            ValueMinLengthRule::NAME => 1,
        ],
    ];

    /* ... */
}

When we put this into practice:

<?php

use DCarbone\PHPFHIRGenerated\Versions\R4\Types\FHIRStringPrimitive;

$stringPrimitive = new FHIRStringPrimitive(value: '');
$errs = $stringPrimitive->_getValidationErrors();
echo "value: '{$stringPrimitive}'\n";
var_export($errs);

/*

value: ''
array (
  'value' => 
  array (
    'value_min_length' => 'Field "value" on type "string-primitive" must be at least 1 characters long, but it is empty',
  ),
)

*/

Above, we created a string primitive with an empty value. The FHIR specification requires that all string values, if defined, must have at least one character in their value. The error map is telling us tha the "value" of the string primitive is not valid, as it is less than 1 characters long.

Lets try again with a "valid" value:

<?php

use DCarbone\PHPFHIRGenerated\Versions\R4\Types\FHIRStringPrimitive;

$stringPrimitive = new FHIRStringPrimitive(value: 'Steve');
$errs = $stringPrimitive->_getValidationErrors();
echo "value: '{$stringPrimitive}'\n";
var_export($errs);

/*

value: 'Steve'
array (
)

*/

As you can see, setting the value to "Steve" and running validations again results in an empty error map as the value "Steve" is at least one character long.

The above is a very simple example, executed directly on a primitive type. Let's see what it looks like with a more complex type:

<?php

use DCarbone\PHPFHIRGenerated\Versions\R4\Types\FHIRElement\FHIRContactDetail;
use DCarbone\PHPFHIRGenerated\Versions\R4\Types\FHIRResource\FHIRDomainResource\FHIRResearchDefinition;

$researchDefinition = new FHIRResearchDefinition(
    contact: [
        new FHIRContactDetail(name: 'Steve')
    ]
);

$errs = $researchDefinition->_getValidationErrors();
echo "value: '{$researchDefinition->getContact()[0]->getName()}'\n";
var_export($errs);

/*
value: 'Steve'
array (
)

*/

Here, we create a ResearchDefinition resource with a singular contact with the name "Steve". Steve is a non-empty value, thus it passes validation.

Let's try with an "empty" value for the contact's name:

use DCarbone\PHPFHIRGenerated\Versions\R4\Types\FHIRElement\FHIRContactDetail;
use DCarbone\PHPFHIRGenerated\Versions\R4\Types\FHIRResource\FHIRDomainResource\FHIRResearchDefinition;

$researchDefinition = new FHIRResearchDefinition(
    contact: [
        new FHIRContactDetail(name: '')
    ]
);

$errs = $researchDefinition->_getValidationErrors();
echo "value: '{$researchDefinition->getContact()[0]->getName()}'\n";
var_export($errs);

/*

value: ''
array (
  'contact.0.name.value.value' => 
  array (
    'value_min_length' => 'Field "value" on type "string-primitive" must be at least 1 characters long, but it is empty',
  ),
)

*/

As you can see, we now have a non-empty error map. The key is the path to the specific field with the value that failed validation, the validator rule that tripped, and a message describing the issue.

Generated Validator Rules

This page is manually updated, and may be out of date depending on when you read this.. You can always find a comprehensive list of validators in the Validation/Rules namespace.

This method returns a map with field path keys, and a map of validator names to error messages per field path. There are a number of validators that are generated with the library, and you may define or override those as you see fit.

max_occurs

Asserts that a given collection field has no more than the specified number of elements.

This rule expects the constraint to either be -1, indicating it may occur an unlimited number of times, or an integer greater or equal to 1.

min_occurs

Asserts that a given collection field is of a specific minimum length.

This rule expects the constraint to be an integer greater than or equal to 1.

value_max_length

Asserts that a given string value is no more than x characters long.

This rule expects the constraint to either be -1, indicating it has no maximum length, or an integer greater than or equal to 1.

value_min_length

Asserts that a given string value is at least x characters long.

This rule expects the constraint to be an integer greater than or equal to 1.

value_one_of

Asserts that a given value is within the expected list of values.

This rule expects the constraint to be an array of allowed string values. Comparison is case-sensitive.

value_pattern_match

Asserts that a given string value matches the specified pattern.

This rule expects the constraint to be a PHP PCRE Expression String.

Pattern Match Considerations

As described in issue #150, the regular expression pattern defined by the R4 and R4B base64Binary type is not particularly efficient. It results in a stack overflow when attempting to parse "large" values.

The expression was updated to a more performant one with the R5 specification, but for compatibility reasons this library retains version-specific type validation constraints.

Creating / Overriding Validator Rules

To create or override a validation rule, you simply need to define a class that implements the RuleInterface.

Once created, you must "register" the rule with the Validator class by calling the Validator::setRule($rule); method.

Example Custom Validation Rule

Lets create a useless validation rule that allows us to limit string values to either === or !== Steve:

<?php

use DCarbone\PHPFHIRGenerated\Types\TypeInterface;
use DCarbone\PHPFHIRGenerated\Validation\RuleInterface;
use DCarbone\PHPFHIRGenerated\Validation\Validator;
use DCarbone\PHPFHIRGenerated\Versions\R4\Types\FHIRStringPrimitive;

// First, create rule class

class OnlySteves implements RuleInterface
{
    public const NAME = 'only_steves';

    public function getName(): string
    {
        return self::NAME;
    }

    public function getDescription(): string
    {
        return 'Only Steves allowed.';
    }

    public function assert(TypeInterface $type, string $field, mixed $constraint, mixed $value): null|string
    {
        $isSteve = $value === 'Steve';
        if ($isSteve && !$constraint) {
            return sprintf('Field "%s" on type "%s" must not be Steve.', $field, $type->_getFHIRTypeName());
        } else if (!$isSteve && $constraint) {
            return sprintf('Field "%s" on type "%s" must be Steve.', $field, $type->_getFHIRTypeName());
        }
        return null;
    }
}

// Next, register with validator

Validator::setRule(new OnlySteves());

// Then, during type initialization, register a new rule for the "value" field with a `true` constraint
// to require that the "value" field only ever equal "Steve":

$stringPrimitive = new FHIRStringPrimitive(value: 'Steve');
$stringPrimitive->_setFieldValidationRule('value', OnlySteves::NAME, true);
$errs = $stringPrimitive->_getValidationErrors();
echo "value: {$stringPrimitive}\n";
var_export($errs);

echo "\n\n";

// Since we created our rule with support for inversion, we can re-use it to require that the value must be
// anything other than "Steve" by passing `false` to the constraint:

$stringPrimitive = new FHIRStringPrimitive(value: 'Steve');
$stringPrimitive->_setFieldValidationRule('value', OnlySteves::NAME, false);
$errs = $stringPrimitive->_getValidationErrors();
echo "value: {$stringPrimitive}\n";
var_export($errs);
Clone this wiki locally