Jump to table of contents

Validation

Data can be validated using the entity schemas or the ValidatorFactory. In both cases, we use Laravel’s Validation library.

All of Laravel’s validation rules are supported except the exist rule.

ValidatorFactory #

The ValidatorFactory class is a wrapper for Laravel’s Validator. It mimics Laravel’s make method to create a validator.

$validator = ValidatorFactory::make($props, $rules);

Run the validator and retrieve errors.

if ($validator->fails()) {
  $errors = $validator->errors();
}

Validate a single value.

$props = ['contactEmail' => $userEmail];
$rules = ['contactEmail' => ['email_or_localhost']];
$validator = ValidatorFactory::make($props, $rules);

Or validate more than one value at a time.

$props = [
  'contactEmail' => $userEmail,
  'contactUsername' => $userName,
];
$rules = [
  'contactEmail' => ['email_or_localhost'],
  'contactUsername' => ['alpha_num'],
];
$validator = ValidatorFactory::make($props, $rules);

The validator will return helpful errors when a value does not validate. If you want, you can customize these messages by passing an additional argument.

$props = ['contactUsername' => $userName];
$rules = ['contactUsername' => ['min:6', 'alpha_num']];
$messages = ['contactUsername.min' => 'The journal contact username must be at least 6 characters.'];
$validator = ValidatorFactory::make($props, $rules, $messages);

Schema Validation #

Validation rules can be defined in an entity’s schema. In this example, the numAnnouncementsHomepage must be an integer that is not negative.

{
  "numAnnouncementsHomepage": {
    "type": "integer",
    "validation": [
      "nullable",
      "min:0"
    ]
  }
}

These rules will be applied when the entity’s Service class validates user input that contains a numAnnouncementsHomepage prop. See the Service Validation section below.

Every property that can be empty or null must have the nullable validation rule assigned, or it will throw an error when it is empty.

{
  "mailingAddress": {
    "type": "string",
    "validation": [
      "nullable"
    ]
  }
}

You never need to add the array, boolean, integer or string validation rules. These will be applied automatically based on the type property.

Service Validation #

An entity’s Service class should implement a validate method which validates props against the schema. Use the SchemaService to access helper methods for working with the schema.

class PKPContextService implements EntityWriteInterface {
	/**
	 * @copydoc \PKP\Services\EntityProperties\EntityWriteInterface::validate()
	 */
	public function validate($action, $props, $allowedLocales, $primaryLocale) {
		$schemaService = Services::get('schema');
		import('lib.pkp.classes.validation.ValidatorFactory');
		$validator = \ValidatorFactory::make(
			$props,
      $schemaService->getValidationRules(SCHEMA_CONTEXT, $allowedLocales)
    );
  }
}

The SchemaService includes a helper method to validate required fields.

if ($action === VALIDATE_ACTION_ADD) {
  \ValidatorFactory::required(
    $validator,
    $schemaService->getRequiredProps(SCHEMA_CONTEXT),
    $schemaService->getMultilingualProps(SCHEMA_CONTEXT),
    $primaryLocale
  );
}

The allowedLocales helper method will throw an error if values are provided for any locales which are not supported by the journal or press.

\ValidatorFactory::allowedLocales(
  $validator,
  $schemaService->getMultilingualProps(SCHEMA_CONTEXT),
  $allowedLocales
);

The requirePrimaryLocale helper method will validate props that should be required in the primary locale, but not required in other locales.

// Require a journal name to be provided in the primary locale
\ValidatorFactory::requirePrimaryLocale(
  $validator,
  ['name'],
  $props,
  $allowedLocales,
  $primaryLocale
);

Some validation rules can not be described in the schema. This is the case when validation requires checking the database. For example, a context can not have a urlPath if another context exists with that urlPath.

In such cases, the Service class’s validate method should be used to extend the validation check.

function validate($action, $props, $allowedLocales, $primaryLocale) {
  $schemaService = Services::get('schema');
  import('lib.pkp.classes.validation.ValidatorFactory');
  $validator = \ValidatorFactory::make(
    $props,
    $schemaService->getValidationRules(SCHEMA_CONTEXT, $allowedLocales)
  );

  ...

  // Ensure that a urlPath, if provided, does not already exist
  $validator->after(function($validator) use ($action, $props) {
    if (isset($props['urlPath']) && !$validator->errors()->get('urlPath')) {
      $contextDao = Application::getContextDAO();
      $contextWithPath = $contextDao->getByPath($props['urlPath']);
      if ($contextWithPath) {
        if (!($action === VALIDATE_ACTION_EDIT
            && isset($props['id'])
            && (int) $contextWithPath->getId() === $props['id'])) {
          $validator->errors()->add('urlPath', __('admin.contexts.form.pathExists'));
        }
      }
    }
  });

  ...

  if ($validator->fails()) {
    ...
  }
}

Custom Rules #

OJS and OMP have added custom validation rules for ISSNs, ORCIDs and more. These rules can be applied in the schema.

{
  "onlineIssn": {
    "type": "string",
    "validation": [
      "nullable",
      "issn"
    ]
  }
}

The following rules have been added:

  • email_or_localhost - Extends Laravel’s email validation to accept emails @localhost.
  • issn - Require a valid ISSN.
  • orcid - Require a valid ORCID.
  • currency - Require a valid currency code.