Concrete CMS includes a powerful composable validation stack that allow developers to build complex reusable validators with minimal code.
Main Concepts
1. Validator
A validator is any class that implements the \Concrete\Core\Validator\ValidatorInterface
interface. Validators have two methods:
public getRequirementsStrings()
Used to provide a list of requirements for validation, like "Must be at least 10 characters" or "Must not contain any newlines".public isValid($value, array $errors)
Used to determine if a value is valid. Any errors raised by validation are added to the$errors
array.
This type of validator is the simplest type of validator and is typically used on its own when things like changing error strings or validating against a subject aren't needed.
1.1 Translatable Validator
Translatable validators are validators that implement the \Concrete\Core\Validator\TranslatableValidatorInterface
interface which extends the Validator interface. Typically a translatable validator should extend AbstractTranslatableValidator
to get some extra functionality like using closures for error strings. Translatable validators have two additional methods:
public setRequirementString($code, $message)
Which allows a developer using an instance of a validator to configure the requirement string.public setErrorString($code, $message)
Which is the same concept assetRequirementString
but for the errors that are omitted when validation fails.
This type of validator is especially useful when a validator can be configured. A good example of this is the RegexValidator
(which we will cover further later), the requirements and errors raised by this validator are completely dependent on the regular expression the developer chooses to use, and so the developer is most capable of providing accurate requirement and error strings.
1.2 Validator For Subject
A validator for subject validator implements the \Concrete\Core\Validator\ValidatorForSubjectInterface
interface which extends the Validator interface and allows validation to happen on a given subject - like a user or a page - rather than validating the string alone. This validator introduces one additional method on top of the Validator interface:
public isValidFor($value, $subject = null, array $errors)
A method that resembles theisValid
method, but allows specifying a subject.
A good example of a validator for subject is the ReuseValidator
which prevents a user from reusing a password. There are no restrictions on what a "subject" can be, so it's up to the implementor to determine what types they'd like to handle.
2. Validator Manager
Validator Managers are used to glue multiple validators together. Concrete provides an implementation that can be used in most cases called \Concrete\Core\Validator\ValidatorManager
but custom ones can be made by implementing the \Concrete\Core\Validator\ValidatorManagerInterface
.
An important thing to remember about ValidatorManagers is that they are Validators themselves and can be nested underneath other validators.
2.1 Validator Manager For Subject
Validator Manager for Subjects are simply ValidatorManagers
that implement ValidatorForSubjectInterface
making it simple to combine many validators and validate against a subject. The core provides an implementation that will work in most cases called \Concrete\Core\Validator\ValidatorForSubjectManager
which gracefully handles nested validators whether or not they support subjects.
3. Validator Stack
"Validator Stack" is a term used to describe a built validator manager that's ready to do some validation. Some examples of core validator stacks are listed below.
Built in Validators / Validator Stacks
The core comes with several useful validators and a few validator stacks. All validators provided by the core are translatable, and some work for subjects:
Validators
- Validator:
EmailValidator
Used to test whether a given value is a valid email address. It can be configured to test MX records to ensure the given domain actually serves email.E_INVALID_ADDRESS
Error for when an email address is not valid.
- Validator:
MaximumLengthValidator
Simple validator to lock a value to configurable maximum length.E_TOO_LONG
Error used when a value is too long.
- Validator:
MinimumLengthValidator
Same as theMaximumLengthValidator
but for a minimum length.E_TOO_SHORT
Error used when a value is too short.
- Validator:
RegexValidator
Allows the instantiator to provide any regex pattern they'd like to validate against. This validator has generic requirement strings and error strings so it's recommended that implementors also define meaningful values for errors.E_DOES_NOT_MATCH
Error used when the value doesn't match the given pattern
- Validator for subject:
ReuseValidator
E_PASSWORD_RECENTLY_USED
Used when a password was used recently
- Validator for subject:
UniqueUserEmailValidator
E_EMAIL_IN_USE
Used when no subject is provided and any user is using the given email address.E_EMAIL_USED_BY_ANOTHER_USER
Used when a subject is provided and a different user is using the given email address.
- Validator for subject:
UniqueUserNameValidator
E_USERNAME_IN_USE
Used when no subject is provided and any user is using the given username.E_USERNAME_USED_BY_ANOTHER_USER
Used when a subject is provided and a different user is using the given username.
Validator Stacks
Validator stacks are stored against the container (typically $app
, or \Core
)
- Manager for subject: validator/password
Used to validate passwords.
- Manager for subject: validator/user/email
Used to validate user emails.
- Manager for subject: validator/user/name
Used to validate usernames.
Using a validator stack
// Assuming you have the container stored in a local variable. If not
// `\Core::make` can be used instead.
$validator = $app->make('validator/password');
// Simplest usage if error messages are not needed
$isValid = $validator->isValid('foo');
// Validate with no subject
$errors = $app->make('error');
if (!$validator->isValid('foo', $errors)) {
echo "Got errors:";
foreach ($errors->getList() as $message) {
echo "<p>{$message}</p>";
}
}
// Validate with a subject
$errors = $app->make('error');
$user = 1; // This can be a User object, a UserInfo object, or a User entity object.
if (!$validator->isValidFor('foo', $user, $errors)) {
echo "Got errors:";
foreach ($errors->getList() as $message) {
echo "<p>{$message}</p>";
}
}
Building your own validator stack
// Assume we want to build a validator stack that ensures we only ever receive
// 10-20 hex characters. We don't need this to be for a subject so we'll just use
// the basic ValidatorManager
$stack = new ValidatorManager();
// We could just rely on regex to validate everything, but that may lead to
// confusing user messages. Instead we'll use a separate validator to validate
// each aspect of our requirements so that we can clearly articulate requirements
// and errors.
// Firstly we need to validate the length
$maxLength = new MaximumLengthValidator(20);
$minLength = new MinimumLengthValidator(10);
$stack->setValidator('minLength', $minLength);
$stack->setValidator('maxLength', $maxLength);
// Next we'll want some regex to validate the input only contains hex
$onlyHex = new RegexValidator('/^[a-fA-F0-9]+$/');
// The generic strings the RegexValidator uses don't really give any information.
// Let's use our own strings instead
$onlyHex->setRequirementString(
$onlyHex::E_DOES_NOT_MATCH,
t('Must only consist of hexadecimal characters A-F and 0-9.')
);
$onlyHex->setErrorString(
$onlyHex::E_DOES_NOT_MATCH,
t('Given string contains characters that are not valid hex.')
);
$stack->setValidator('onlyHex', $onlyHex);
Now we can use our stack to validate values:
$errors = $app->make('error');
$isValid = $stack->isValid('Foo', $errors);
foreach ($errors->getList() as $error) {
echo "<p>{$error}</p>\n";
}
If we run that, we'd expect to see the following:
<p>String "Foo" must be at least 10 characters long.</p>
<p>Given string contains characters that are not valid hex.</p>
One thing we notice is our error string is a little more generic than the one provided by the minimum length validator. So let's configure our error message to be more specific:
$onlyHex = $stack->getValidator('onlyHex');
$onlyHex->setErrorString($onlyHex::E_DOES_NOT_MATCH, function($validator, $error, $value) {
$nonHexCharacters = str_split(preg_replace("/[a-fA-F0-9]/", "", $value));
return t(
'String "%s" contains the following non-hex characters: %s',
$value,
trim(json_encode(array_unique($nonHexCharacters)), '[]'),
);
});
Now if we run the same test above we should see the following output:
<p>String "Foo" must be at least 10 characters long.</p>
<p>String "Foo" contains the following non-hex characters: "o"</p>