Creating Custom Interfaces

Learn how to create custom user interfaces in Oracle Communications Unified Assurance.

You should only consider the following as reasons for creating a custom interface:

If an existing interface or DDO can be tweaked to achieve the functionality, you can override the existing interface instead. See Overriding a User Interface for more details.

Once implemented, a custom interface can be seamlessly integrated into the Unified Assurance platform using a variety of methods:

About SDK Package Architecture

Unified Assurance UIs follow the model-view-controller architecture guidelines. The code is organized into the following types:

Generally, each custom package should have the following:

Sometimes, there will be common core functionalities used in the controller that can be used across multiple custom packages. These can be placed in a Javascript file in the $A1BaseDir/www/packages/custom/mixin directory.

The folder and file structure of a custom UI package should be similar to the following:

where component A and component B are views or pages in your custom interface.

Example:

Naming Conventions

Note:

Avoid using numbers in all names as it may cause errors.

About the Model

The model consists of an ExtJS store model, which should reside in the model directory.

The ExtJS model defines where to request data and provides a list of fields that you expect to receive from the API as part of its response. If the exact field is not listed, incoming data from the API for unlisted fields are ignored by the form and grids.

The ExtJS model file contains two important definitions:

ExtJS Model

Ext.define('<CLASSNAME>', {
    extend:     'Ext.data.Model',
    idProperty: '<IDFIELD>',
    fields: [
        <FIELD>,
         ...
        <FIELD>
    ],
    proxy: {
        type: 'rest',
        url:  '/api/<PACKAGE>/<API>',
        reader: {
            type: 'json',
            root: 'data'
        }
    }
});

where:

For example:

Ext.define('Custom.customTable.crud.model.CustomTableModel', {
    extend:     'CoreWebLib.baseCrud.crud.model.Model',
    idProperty: null,
    fields: [
        'DeviceName',
        {
            name: 'GeoLocation',
            convert: function(data) {
                if (data === undefined || data === null) {
                    return '{"type": "Point", "coordinates": [0,0]}'
                } else if (typeof(data) === 'string') {
                    return data
                }
                else {
                    return Ext.encode(data)
                }
            }
        }
    ],
    proxy: {
        type: 'rest',
        url:  '/api/custom/CustomTables',
        reader: {
            type:         'json',
            rootProperty: 'data'
        }
    }
});

API

The API folder contains a PHP file that defines the API. This is the API that the model calls for data.

You can use custom APIs to subclass an existing Unified Assurance API, similar to Overriding a User Interface, or create a REST API class from scratch. If you are creating from scratch, your custom API should comply with REST guidelines like Unified Assurance APIs do, and it should extend the base API class Model to ensure access to validation libraries, request and response objects, and so on.

Note:

APIs follow the following structure:

<?php

 special parent classes
<PARENTREQUIRES>
 ...

class <CLASSNAME> extends <PARENTCLASS> {
    //disable permission checking on this class
    public $skipSessionValidation = true;

    /**
     * ID specified for individual CRUD operations
     *
     * @var int
     */
    public $id;

    <REQUESTPARAMS>
     ...

    public $validations = [
        <VALIDATIONS>
    ];

    protected $filterConversions = [
        <FILTERS>
    ];

    <APIFUNCTIONS>
     ...

    <VALIDATIONFUNCTIONS>
     ...
}
?>

where:

For example:

<?php

class CustomTable extends Model
{

    public $skipSessionValidation = true;

    protected $filterConversions = [
                'TimestampAdded' => 'DATE(FROM_UNIXTIME(TimestampAdded))',
    ];


    public function read()
    {
        $this->response->total = 0;

        // Retrieve single device
        if (isset($this->id) && $this->isIdNumeric($this->id)) {
            $this->filter[] = (object)[
                "property" => "DeviceID",
                "value"    => $this->id,
                "operator" => "eq"
            ];
        }

        $exampleSQL = "
                SELECT DeviceName,
                       ST_AsGeoJson(GeoLocation)       AS GeoLocation,
                       FROM_UNIXTIME(D.TimestampAdded) AS TimestampAdded
                  FROM Devices
            ";

        $exampleSQL = $this->addQueryFilter($exampleSQL);
        $exampleSQL = $this->addQuerySort($exampleSQL);
        $exampleSQL = $this->addQueryLimit($exampleSQL);

        $exampleStatement = $this->DBH->prepare($exampleSQL);

        if ($exampleStatement->execute()) {
            $rows = $exampleStatement->fetchAll(PDO::FETCH_ASSOC);
            $rowCount = count($rows);

            $this->response->success = true;
            $this->response->message = tr('Loaded %d entries', $rowCount);
            $this->response->data    = $rows;
            $this->response->total   = $this->foundRows();
        } else {
            $this->response->success = false;
            $this->response->message = tr('Failed to load data');
        }

        $exampleStatement = null;
    }
}

About the View

The view portion of the interface usually consists of a container index which holds a grid and a form piece. Depending on the use of the interface, the grid and form are optional. But generally, if intended as a CRUD (Create, Read, Update, Delete) interface, it should implement the index, grid, and form parts.

Grid.js

The grid is the main part of an interface. It can display data in a tabular format. See ExtJS documentation on "grid" xtypes for more information on the options used in these examples.

Some important sections of the grid ExtJS file are:

The tabular grid displays summary data columns row by row and supports filtering and sorting. Tabular grids follow the following structure:

Ext.define('<CLASSNAME>', {
    extend:  'CoreWebLib.baseCrud.crud.view.Grid',
    alias 'widget.<ALIAS>',
    title '<TITLE>',
    iconCls: 'fa fa-table',
    stateful: true,
    stateId:  '<STATEID>',
    requires: [
        <REQUIRES>
    ],
    store: {
        model:        '<MODEL>',
        pageSize:     100,
        remoteFilter: true,
        remoteSort:   true,
        sorters: [
            <SORTERS>
            ...
        ]
    },
    columns = [
        <COLUMNDEF>
        ...
    ],
    toolbarItems: [
        <GRIDBUTTONS>
    ]
});

where:

For example:

Ext.define('Custom.customTable.crud.view.CustomTableGrid', {
    extend:     'CoreWebLib.baseCrud.crud.view.Grid',
    alias:      'widget.customTable.grid',
    controller: 'Custom.customTable.crud.controller.CustomTableGridController',

    requires:   [
        'Custom.customTable.crud.model.CustomTableModel',
        'Custom.customTable.crud.controller.CustomTableGridController'
    ],
    title:       tr('Example Table'),
    iconCls:     'fa fa-table',
    stateful:    true,
    stateId:     'custom-customTable-grid',
    packageName: 'custom',
    viewName:    'Example',

    store: {
        model:        'Custom.customTable.crud.model.CustomTableModel',
        pageSize:     100,
        remoteFilter: true,
        remoteSort:   true,
        sorters: [
            {
                property : 'DeviceName',
                direction: 'ASC'
            }
        ]
    },

    toolbarItems: [
        {
            iconCls:    'fa fa-line-chart',
            text:       tr('Show Message'),
            handler:    'onShowClicked'
        },
                        {
                            xtype:          'relativetime',
                            name:           'TimeRange',
                            fieldLabel:     tr('Time Range'),
                            queryMode:      'local',
                            forceSelection: true,
                            triggerAction:  'all',
                            value:          'now-24h..now',
                            width:          150
                        }
    ],

    columns:    [
        {
            header:    tr('Name'),
            dataIndex: 'DeviceName',
            width:     500,
            filter:    'string'
        },
        {
            header: tr('GeoLocation'),
            dataIndex: 'GeoLocation',
            filter: 'string',
            width:  800,
        }
    ]
});

View.js

The view is the main page of a custom UI and is a container for the grid and forms components, if present. Unified Assurance views use the border layout for arrangement. The view can be entirely customized in what it displays but should contain at least one item. See ExtJS documentation for more information on the Ext.container.Container class.

Ext.define('<CLASSNAME>', {
    extend: 'Ext.container.Container',
    alias:  'widget.<ALIAS>',
    title:  '<TABTITLE>',
    requires: [
        '<REQUIRES>'
    ],
    gridClass:  '<GRIDCLASSS>',
    formClass:  '<FORMCLASSS>',
    breadcrumbs: [{
        text: '<TABTITLE>',
        href: '#'
    }]
});

where:

For example:

Ext.define('Custom.customTable.crud.view.CustomTableView', {
    extend:   'CoreWebLib.baseCrud.crud.view.CustomView',
    alias:    'widget.customTableview',
    requires: [
        'Custom.customTable.crud.view.CustomTableGrid'
    ],

    gridClass:  'Custom.customTable.crud.view.CustomTableGrid',
    formClass:  false,
    title: tr('Custom Table'),
    breadcrumbs: [{
        text: tr('Custom Table'),
        href: '#'
    }]
});

About the Controller

The controller is the centerpiece of an interface. It houses the listener callbacks, sends the API requests, and initializes the view pieces that make up the interface and its functionality. Unified Assurance provides a few default controllers with common functionality built in for specific types of controller. See Unified Assurance Controllers for available classes and functionality from which to extend your controller.

The controller follows the following structure:

Ext.define('<CLASSNAME>', {
    extend:   '<CONTROLLERLIB>',
});

where:

For example:

Ext.define('Custom.customTable.crud.controller.CustomTableGridController', {
    extend:                 'CoreWebLib.baseCrud.crud.controller.GridController',
    alias:                  'controller.Custom.customTable.crud.controller.CustomTableGridController',
    mixins: [
        'Custom.mixin.CustomMixin'
    ],
    listen:            {
        component: {
            'relativetime': {
                change: 'onTimeRangeChange'
            }
        }
    },
    onShowClicked:      function (button, event) {
        var selection = button.up('panel[itemId=grid]').getSelection(),
            timeRange = button.up('panel[itemId=grid]').down('relativetime');

        console.log(timeRange.getValue());
        if (selection[0]) {
            var selectedDeviceNames = '';
            selection.forEach(function(element, index, array){
                if (index === 0) {
                    selectedDeviceNames += element.get('DeviceName');
                } else if (index < selection.length) {
                    selectedDeviceNames += ', ' + element.get('DeviceName');
                } else {
                    selectedDeviceNames += element.get('DeviceName');
                }
            })
            this.showToast('Selected device(s): ' + selectedDeviceNames)
        } else {
            this.showToast('No device(s) selected');
        }
    },
    onTimeRangeChange: function (timerange) {
        console.log("Time Range Changed");
        console.log(timerange.getValue());
        console.trace();
    }
});

With the new custom interface built, you may need to access it through the Links menu. You can add the link using the UI and insert the data using a SQL request.

To add the custom interface to the Links menu and insert the data:

  1. Place your files on the Unified Assurance presentation server under $A1BASEDIR/www/packages.

  2. Run an SQL request to add relevant data. The structure of the SQL request should be:

    INSERT INTO UIExtraPaths (Namespace, Path) values ('<CLASS PREFIX>', 'ui/<PACKAGE>/<SUBFOLDER>');
    INSERT INTO UIExtraRequires (Class) values ('<FULL CLASS>');
    

    where:

    • <PACKAGE> is the new UI package without type suffix.

    • <SUBFOLDER> is the optional subfolder if separating projects within the package.

    • <CLASS PREFIX> is the ExtJS class prefix that is the package name in upper camel case and any subfolders before the MVC folders.

    • <FULL CLASS> is the full ExtJS class name based on the directory path. An entry must be made for each ExtJS class created for the project.

    For example:

    INSERT INTO UIExtraPaths (Namespace, Path) values ('Custom', 'packages/custom');
    INSERT INTO UIExtraRequires (Class) values ('Custom.customTable.crud.view.CustomTableView');
    INSERT INTO UIExtraRequires (Class) values ('Custom.customTable.crud.view.CustomTableGrid');
    INSERT INTO UIExtraRequires (Class) values ('Custom.customTable.crud.controller.CustomTableGridController');
    INSERT INTO UIExtraRequires (Class) values ('Custom.customTable.crud.model.CustomTableModel');
    INSERT INTO UIExtraRequires (Class) values ('Custom.mixin.CustomMixin');
    
  3. From the Configuration menu, go to Links, then click Links to go to the Links interface, and click Add.

  4. Enter the following information:

    • Name: <NAME>, where <NAME> is the name of the project. For example, CustomTable.

    • Type: Internal

    • Path: <PATH>, where <PATH> is the Unified Assurance internal path to the interface's controller, following the format <PACKAGE>/. For example, custom/src/customTable/crud/controller/CustomTableGridController.

    • Icon: <ICON>, where <ICON> is your choice of icon.

    • User Owner: Set to Public to All Users In Group or restrict to a specific user.

    • Group Owner: Set to Public to All Groups or restrict to a specific group.

    • Viewers: Optionally, select a group to grant read-only access to.

  5. Click Submit.

  6. Verify that the link was created.

  7. From the Configuration menu, go to Links, then click on Link Groups to go to the Link Groups interface.

  8. Select the Root group in the grid.

  9. Switch the multiselector from Selected to Available.

  10. Double-click the name of the link created earlier.

  11. Click Submit.

  12. Go back to the Links UI (as in step 3).

  13. Click the name of the link created earlier in the grid. The custom defined table should load.

Custom Buttons and Listeners

There are several cases where your custom UI may require extra functionality not covered by Unified Assurance default controllers, such as a new button in a grid that performs a non-CRUD action. Regardless of what type of functionality is required for the custom UI, all listeners and handlers should be defined in the Controller class and any viewable items should be in the appropriate View class. ExtJS provides a unique searching system to find the correct dom elements within strings. It will check dom elements against certain criteria before returning it. Criteria are checked against element attributes, types and names, but attributes can be specifically defined within square brackets using the format:

<NAME/TYPE/CLASS/ALIAS>[<ATTRIB>=]

For example, to search for the Submit button in the Requests form, define button[action=submit], or to find the grid, define <BASENAME>grid.

Starting with the Requests example as its base, the following example adds a button to the Grid's renderToolbar() function to open an email with the details of the selected request:

Grid

 ...
    renderToolbar: function() {
        return {
            xtype: 'toolbar',
            items: [
                {
                    iconCls: 'icon-add',
                    text:    'Add',
                    action:  'add'
                },
                {
                    iconCls:  'icon-clone',
                    text:     'Clone',
                    action:   'clone',
                    disabled: true
                },
                {
                    iconCls:  'icon-delete',
                    text:     'Delete',
                    action:   'delete',
                    disabled: true
                },
                {
                    iconCls:  'icon-mail',
                    text:     'Email',
                    action:   'email',
                    disabled: true
                }
            ]
        };
    }
 ...

Controller

 ...
    init: function(viewID, action) {
        //Add listener for new email button
        this.listeners[this.baseName + 'grid button[action=email]'] = {
            click: this.onEmailRequest
        };

        this.callParent(arguments);
    },
 ...

    /*
     * override parent CoreWebLib.baseCrud.crud.controller.GridController onSelectRow() so the new 'email' button is enabled
     * when a request is selected. ExtJS dom navigation possible through up() and down() to the
     * elements of our view. Anything the parent doesn't know about will need to be handled special.
     */
    onSelectRow: function(gridView, record) {
        var grid             = gridView.up(this.baseName + 'grid'),
            selections       = grid.getSelectionModel().getSelection(),
            emailButton      = grid.down('button[action=email]');

        //disable button unless there is only one item selected
        if (emailButton) {
            emailButton.setDisabled(selections.length != 1);
        }

        //let parent do the rest
        this.callParent(arguments);
    },
 ...

    //handler function
    onEmailRequest: function(button) {
        var grid = button.up(this.baseName + 'grid');

        //get the selection for values
        var request = grid.getSelectionModel().getSelection()[0];

        //use a hidden iframe to setup the 'mailto:', the browser will take care of the rest
        var exportFrame = Ext.get('export-frame').dom;

        //build the email with the selection
        var url = 'mailto:?' + 'subject=' + request.RequestName +
                  '&' + 'body=' + request.RequestDetails;

        exportFrame.src = url;
    },
 ...

Similarly, listeners can be removed by deleting the listeners entry in the init function of the controller. This is commonly used when an interface does not implement a default CRUD component such as a button.

delete this.listeners[<SEARCH>];

Example:

 ...
    init: function(viewID, action) {
        //remove listener for the add grid button
        delete this.listeners[this.baseName + 'grid button[action=add]'];

        this.callParent(arguments);
    },
 ...

Validation

Unified Assurance supports both client-side and server-side validation for creates and updates. Supported client-side validations are defined in the API model's $validations array and applied directly to the form. Server-side validation is implemented with custom functions. This allows the custom UI to have one set of validations that will apply regardless of whether it is called from the form or from another API.

The types of client-side validation supported by Unified Assurance API are:

Validation entries are keyed on the API parameter they validate, that is, the key should match the defined class variable names. For example, for public $DeviceName;, the key would be DeviceName.

    public $validations = [
        '<PARAM>' => [
            'required' => '<REQUIRED>',
            'vtype'    => '<VTYPE>',
            <VTYPESPECIFIC>
             ...
            'dbValidation' => '<DBVALIDATION>'
        ]
        ...
    ];

where:

Example:

    public $validations = [
        'RequestName' => [
            'vtype'        => 'alphanum',
            'allowBlank'   => false,
            'dbValidation' => 'validateRequestName'
        ],
        'RequestType' => [
            'vtype'      => 'number',
            'allowBlank' => false,
            'minValue'   => 1,
            'maxValue'   => 3
        ],
        'RequestEmail' => [
            'vtype'      => 'email',
            'allowBlank' => false
        ],
        RequestDetails => [
            'required' => 'notRequired'
        ]
    ];

Note:

Permission and Multitenant Access Checks

While a Custom UI cannot support its own set of custom permissions at this time, it can certainly use existing permissions to restrict access. Permissions for a user are stored in the session and available to the UI and API accordingly using "Assure1.Session.Permissions" variable; permissions are stored by package -> controller -> action flags. Access can be restricted on buttons, features, or even columns and fields based on requirements. Note that not all package controllers support all permissions. Review the specific role permissions in the Roles interface.

General permission types and access:

ExtJS

Example snippet to restrict submit action when rendering buttons:

 ...
    renderButtons: function() {
        var buttons = [
            {
                xtype: 'tberror',
                flex:  1
            }
        ];

        var permission = Assure1.Session.Permissions.device;
        if (permission && permission.Devices && (permission.Devices.create || permission.Devices.update)) {
            buttons.push(
                {
                    text:    'Reset',
                    iconCls: 'icon-reset',
                    action:  'reset'
                },
                {
                    text:    'Submit',
                    iconCls: 'icon-submit',
                    action:  'submit',
                    formBind: true,
                    disabled: true
                }
            );
        }

        return buttons;
    }
 ...

Example snippet to restrict access to the add, clone, delete buttons:

 ...
    renderToolbar: function() {
        var permission = Assure1.Session.Permissions.device;

        if (permission && (permission.Devices && (permission.Devices.create || permission.Devices.delete))) {
            return {
                xtype: 'toolbar',
                items: [
                    {
                        iconCls: 'icon-add',
                        text:    'Add',
                        action:  'add',
                        hidden:  permission.Devices.create ? false : true
                    },
                    {
                        iconCls:  'icon-clone',
                        text:     'Clone',
                        action:   'clone',
                        disabled: true,
                        hidden:   permission.Devices.create ? false : true
                    },
                    {
                        iconCls:  'icon-delete',
                        text:     'Delete',
                        action:   'delete',
                        disabled: true,
                        hidden:   permission.Devices.delete ? false : true
                    }
                ]
            };
        }
        else {
            return null;
        }
    }
 ...

API

Permissions are NOT checked by default in custom UIs and access checks should be similarly validated using the appropriate Permission and Multitenant Access Checks built-in functions.

Packaging for Testing & Release

Custom UIs should be packaged as the next step in testing and release. Packaging your custom UI not only allows for easy installation on any Unified Assurance servers, but it also provides versioning and maintainability. Custom UIs should consist of at least two packages for maintainability; one of SQL schema changes ('-schemaDB') and one of MVC files ('-ui'). Other package types should be created as necessary based on content. See Creating Custom Packages for all the package types and how to create.

Schema DB should contain:

UI package should contain:

Best Practices

Troubleshooting

Type Symptom/Error Description
API response PHP Warning
  • /opt/assure1/www/api/Controller.php on line 30
  • require(/opt/assure1/www/api/../ui/broker/model/Server.php): failed to open stream: No such file or directory.
Opens Form for edit loading the selected record, enabling buttons based on selection, etc.
Custom UI during navigation to interface Does not navigate. Browser developer's console has an error "Uncaught TypeError: c is not a function" The UI's controller couldn't be found. Check that the custom controller path was added to the namespace in "UIExtraPaths" and classes added to "UIExtraRequires" tables during Navigation setup.
Custom UI during navigation to interface Partial load of interface, grid or form stay in "loading" state for a while, etc. Browser developer's console has a syntax error in one of the component files. Syntax error in one or more ExtJS files, review code.
Custom UI during navigation to interface Does not navigate, partial load of interface, grid or form stay in "loading" state for a while, etc. Browser developer's console has an XHR error "GET XXXXX 404 (Not Found)". Could not find one or more ExtJS components. Check classnames, path, package path was added to the namespace in "UIExtraPaths" and classes added to "UIExtraRequires" tables during Navigation setup.

Appendix

Unified Assurance Validations

Data Type, Integrity and Format

VTypes

Case sensitive vtypes

'vtype' => '<VTYPEFUNCTION>'

<VTYPEFUNCTION> Description Example
alpha This field should only contain letters and _ apple
alphanum This field should only contain letters, numbers and _ apple123
email This field should be an e-mail address in the format "user@example.com" apple@example.com
emailList comma separated list of emails apple@example.com, orange@example.com
url This field should be a URL in the format "http://www.example.com" http://www.example.com
cron This field should only contain a number (or *) and optionally a range, list, or interval /10 3 * *
number This field should only contain positive or negative integers 10
numberList comma separated list of positive and negative integers 10, 5, -1
numberFloat This field should only contain numbers and an optional decimal point 10.5
numberBoolean This field should only contain the numbers 0 or 1 1
filename This field cannot contain any common file system special characters such as colon (:), pipe (\ ), etc. myfile.txt
daterange Accepts a date in epoch format. Requires a parameter "StartDate" be present and validates this param's value is less than StartDate's 1443060000
ipv4address This field must be a numeric IPv4 address 192.0.2.1
ipv4addressList comma separated list of IPv4 addresses 192.0.2.1, 192.0.2.1, 192.0.2.3
ipv4addressRange This field must be an IPv4 address range in glob format 192.0-10.2.*
ipv4CIDRAddress This field must be a valid IPv4 address range in CIDR format 192.0.2.0/8
ipv6address This field must be a numeric IPv6 address 2001:DB8::64
ipv6addressList comma separated list of IPv6 addresses 2001:DB8::64, 2001:DB8::C8
ipv6addressRange This field must be an IPv6 address range in glob format 2001:DB8::100-103
ipv6CIDRAddress This field must be a valid IPv6 address range in CIDR format 2001:DB8::/32
ipAddress This field must satisfy either ipv4address or ipv6address 192.0.2.1
ipAddressRange This field must satisfy either ipv4addressRange or ipv6addressRange 192.0-10.2.*
ipAddressList This field must satisfy either ipv4addressList or ipv6addressList 192.0.2.1, 192.0.2.1, 192.0.2.3
ipCIDRAddress This field must satisfy either ipv4CIDRAddress or ipv6CIDRAddress 192.0.2.0/8
metricPeriod This field must be a valid string metric period: Hourly, Daily, Weekly, Monthly, Yearly Daily
fqdn This field must be a valid FQDN hostname.example.com
fqdnList comma separated list of FQDNs hostname.example.com, hostname2.example.com
macAddress This field must be a valid MAC Address 11:22:33:AA:BB:CC
VType Specific

Case-sensitive validations based on specific vtypes

'<SPECIFIC>' => <VALUE>

<SPECIFIC> <VTYPE> <VALUE> Data Description Example
allowBlank Any Boolean (true/false) Indicates whether parameter can be blank or NULL. 'allowBlank' => true
maxLength Alpha, alphanum, string etc. Integer Maximum character length of the string. Commonly used to limit value when inserting into database fields. 'maxLength' => 255
minLength Alpha, alphanum, string etc. Integer Minimum character length of the string. 'minLength' => 3
maxValue Number, numberFloat, etc. Float Maximum value of a numeric field. 'maxValue' => 100
minValue Number, numberFloat, etc. Float Minimum value of a numeric field. 'minValue' => 0

required

Custom server-side validation on whether the parameter is required. Commonly used when validating interdependent params are present such as requiring certain fields if other fields are set to a value. Should be defined within the same API class that uses it in Validation to maintain access to class parameters. Generally follows the given template:

 ...
    /**
     * <DOCS>
     */
    public function <VALIDATIONNAME>() {
        <VALIDATIONCHECKS>
    }
 ...

It returns:

  1. bool $required: true if required, false if not

The synopsis of required is:

 ...
    /**
     * Require email if of type Complaint
     *
     * @ignore
     * @uses   RequestType
     * @return bool
     */
    public function validateRequestEmailRequirement() {
        if ($this->RequestType == 2) {
            return true;
        }
        else {
            return false;
        }
    }
 ...

dbValidation

Custom server-side validation function. Commonly used to verify uniqueness, data existence, or other checks. Should be defined within the same API class that uses it in Validation to maintain access to class parameters. Generally follows the given template:

 ...
    /**
     * <DOCS>
    */
    public function <VALIDATIONNAME>() {
        //set defaults
        $valid     = false;
        $errorText = '<FAILUREMESSAGE>';

        <VALIDATIONCHECKS>

        return [$valid, $errorText];
    }
 ...

It must return array tuple:

Example:

return [$valid, $errorText];

The synopsis of dbValidation is:

 ...
    /**
     * Validate Request Name
     *
     * @ignore
     * @uses   $id
     * @uses   $RequestName
     * @return array    Validation and error text
    */
    public function validateRequestName() {
        $valid          = false;
        $errorText      = 'This RequestName already exists';
        $queryParams    = [$this->RequestName];
        $RequestSQL = '
            SELECT COUNT(*)
              FROM UserRequests
             WHERE RequestName = ?
        ';

         updates
        if (isset($this->id)) {
            $RequestSQL .= '
                   AND RequestID != ?
            ';

            $queryParams[] = $this->id;
        }

        $RequestStatement = $this->DBH->prepare($RequestSQL);
        if ($RequestStatement->execute($queryParams)) {
            $count = $RequestStatement->fetchColumn();

            if ($count == 0) {
                $valid = true;
            }
        }
        $RequestStatement = null;

        return [$valid, $errorText];
    }
 ...

Additional Resources