Async Validators option

1. Introduction

In SuiteCRM 8, you can add save validators that run on the backend (a.k.a. asynchronous validators) to fields in your modules. This allows you to perform custom validation logic that runs asynchronously on save.

This guide will show you how to create an async validator for a field.

2. Async Validator Metadata Definition

The first thing to define is the asyncValidators entry in the metadata.

The configuration for the logic can be added to the vardefs.php or the detailviewdefs.php.

In the following example we are going to add one to the Accounts module field vardefs.

2.1 Steps to add the logic on the custom vardefs

  1. Create a new field in custom extensions for Accounts module:

    1. Example: public/legacy/custom/Extension/modules/Accounts/Ext/Vardefs/annual_revenue.php

  2. Copy the phone_office definition from public/legacy/include/SugarObjects/templates/company/vardefs.php

  3. Replace the logic entry to the phone_office definition with the code on the snippet below.

    • Re-set permissions if needed (will depend on your setup)

  4. Run Repair and Rebuild on Admin menu

<?php

$dictionary['Account']['fields']['phone_office'] = array(
    'name' => 'phone_office',
    'vname' => 'LBL_PHONE_OFFICE',
    'type' => 'phone',
    'dbType' => 'varchar',
    'len' => 100,
    'audited' => true,
    'unified_search' => true,
    'full_text_search' => array('boost' => 1),
    'comment' => 'The office phone number',
    'merge_filter' => 'enabled',
    'asyncValidators' => [
        'my-async-validator' => [
            'key' => 'my-async-validator',
        ],
    ],
);

2.2 Steps to add a new field async validator

As a best practice extension backend code should be added to extensions/<your-extension>/backend/ or extensions/<your-extension>/modules/. For extensions/<your-extension>/backend/ the subfolder should follow the same structure as used in core/backend

  1. Create the folder extensions/defaultExt/modules/Accounts/LegacyHandler/Validation/Validators/

    1. This is a best practice not a hard requirement

    2. As long as you add under the extensions/<your-ext>/backend or extensions/<your-ext>/modules it should work.

  2. Within that folder create the PhoneAsyncValidator.php, i.e. extensions/defaultExt/modules/Accounts/LegacyHandler/Validation/Validators/PhoneAsyncValidator.php

    1. If you are not using the recommended path, make sure that the namespace follows the one you are using

    2. On our example the namespace is namespace App\Extension\defaultExt\modules\Accounts\LegacyHandler\Validation\Validators;

  3. On PhoneAsyncValidator.php, add the code on the snippet on 2.3 Async Validator implementation section

  4. Run ./bin/console cache:clear or delete the contents of the cache folder under the root of the project

  5. Re-set the correct file permissions if you need to (This will depend on your setup and the user you are using to make changes)

2.3 Async Validator implementation

To add a AsyncValidator the class needs to implement the ProcessHandlerInterface. Plus for it to be matched with a request, it needs the following:

  • Set the ProcessType to be the same as the value that was defined on the metadata (or other), in this example it is my-async-validator

The following snippet has sample implementation of the process handler:

<?php

namespace App\Extension\defaultExt\backend\modules\Accounts\LegacyHandler\Validation\Validators;

use ApiPlatform\Exception\InvalidArgumentException;
use App\Data\LegacyHandler\PreparedStatementHandler;
use App\Process\Entity\Process;
use App\Process\Service\ProcessHandlerInterface;
use Psr\Log\LoggerInterface;

class PhoneAsyncValidator implements ProcessHandlerInterface
{

    protected const MSG_OPTIONS_NOT_FOUND = 'Process options is not defined';
    protected const MSG_VALUE_NOT_DEFINED = 'Field Value is not defined';
    protected const PROCESS_TYPE = 'my-async-validator';

    public function __construct(
        protected PreparedStatementHandler $preparedStatementHandler,
        protected LoggerInterface $logger
    )
    {
    }

    public function getHandlerKey(): string
    {
        return self::PROCESS_TYPE;
    }

    public function getProcessType(): string
    {
        return self::PROCESS_TYPE;
    }

    public function requiredAuthRole(): string
    {
        return 'ROLE_USER';
    }

    public function getRequiredACLs(Process $process): array
    {
        return [];
    }

    public function configure(Process $process): void
    {
        $process->setId(self::PROCESS_TYPE);
        $process->setAsync(false);
    }

    public function validate(Process $process): void
    {
        if (empty($process->getOptions())) {
            throw new InvalidArgumentException(self::MSG_OPTIONS_NOT_FOUND);
        }

        if (!isset($process->getOptions()['value'])){
            throw new InvalidArgumentException(self::MSG_VALUE_NOT_DEFINED);
        }
    }

    public function run(Process $process): void
    {
        $options = $process->getOptions();

        $value = $options['value'] ?? '';

        $data = [
            'errors' => []
        ];

        if ($value === '000-000-0000') {
            $data['errors'][] = 'The street address is invalid';
            $process->setData($data);
            $process->setStatus('error');
            return;
        }

        $process->setStatus('success');
    }
}

2.3.1 Process Handler implementation

2.3.1.1 getProcessType()

This method should return the identifier of the process the handler implements. The same that is defined on the metadata logic key entry. In our example: case-calculate-priority

2.3.1.2 requiredAuthRole()

This method defines if the auth role that is required to have access to the process.

  • 'ROLE_USER' means that the user needs to be logged in to have access to this process.

  • 'ROLE_ADMIN' means that the user needs to be logged in as an admin user to have access to this process.

  • '' means that a non-authenticated user can access the process.

2.3.1.3 getRequiredACLs()

This method defines the SuiteCRM ACLs that are required to have access to the process. 2.2.1.3 getRequiredACLs() See more information 2.2 Process handler implementation

2.3.1.4 validate()

This method is where we should add the code to validate the process inputs.

If the inputs aren’t valid it should throw a InvalidArgumentException.

2.3.1.5 run()

This method is where we add the code to run the logic that our ProcessHandler is supposed to do.

This method does not return anything. Instead, it should update the $process argument that is passed by reference.

2.3.1.5.1 Available Data

Using the $process→getOptions() method we can get the following options that were sent on the request:

  • value: Current field value

  • definition: Field definition from vardefs

  • attributes: All record attributes

  • params: Custom parameters from validator configuration

Setting response result and message

The following are some examples of how to set the feedback for the response.

  • To set the process as successful:

        $process->setStatus('success');
  • To set the process as error:

            $process->setStatus('error');
            $data = [
                'errors' => [
                    'startLabelKey' => 'LABEL_TO_SHOW_BEFORE_ICON',
                    'icon' => 'unsubscribe', // optional icon in error message
                    'endLabelKey' => 'LABEL_TO_SHOW_AFTER_ICON'
                ]
            ];
            $process->setData($data);

Content is available under GNU Free Documentation License 1.3 or later unless otherwise noted.