Let the platform do the work

Extending SugarBPM (Process Manager)

Introduction

From its earliest version, Sugar has been a tool that allows for customizing and extending in a way that allows developers to shape and mold it to a specific business need. Most components of Sugar are customizable or extensible through the use of custom classes (for custom functionality) or extensions (for custom metadata). However, a recent addition to the product, the SugarBPM™ automation suite, did not fit this paradigm. In response, the Process Manager Library was introduced to give developers the ability to customize any portion of SugarBPM where object instantiation was previously handled using the 'new' operator. 

Note: SugarBPM™ is not available in Sugar Professional. For an introduction to the SugarBPM modules and architecture, please refer to the SugarBPM documentation in this guide.

Process Manager

By way of ProcessManager\Factory we can now develop custom versions of most any PMSE* class and have that class used in place of the core PMSE* class.

Limitations

The current implementation of the Process Manager Library only handles server-side customizations by way of the Factory class. These customizations can be applied to any instantiable pmse_* object, however, static classes and singleton classes, such as the following, are not affected by the library:

  • PMSE
  • PMSEEngineUtils
  • PMSELogger

While some effort has been made to allow for the creation of custom actions in the Process Definition designer, as of Sugar 7.8, that is the only client customization allowance that will be implemented.

Library Structure

The Process Manager Library is placed under the Sugarcrm\Sugarcrm\ProcessManager namespace and contains the following directories and classes:

  • Factory
  • Exception/
    • BaseException
    • DateTimeException
    • ExceptionInterface
    • ExecutionException
    • InvalidDataException
    • NotAuthorizedException
    • RuntimeException
  • Field/
    • Evaluator/
      • AbstractEvaluator
      • Base
      • Currency
      • Datetime
      • Decimal
      • EvaluatorInterface
      • Int
      • Multienum
      • Relate
  • Registry/
    • Registry
    • RegistryInterface

Examples

Getting a SugarBPM object

Almost all objects in SugarBPM can be instantiated through the Process Manager Factory. When loading a class, the Factory getPMSEObject method will look in the following directories as well as the custom versions of these directories for files that match the object name being loaded:

  • modules/pmse_Business_Rules/
  • modules/pmse_Business_Rules/clients/base/api/
  • modules/pmse_Emails_Templates/
  • modules/pmse_Emails_Templates/clients/base/api/
  • modules/pmse_Inbox/clients/base/api/
  • modules/pmse_Inbox/engine/
  • modules/pmse_Inbox/engine/parser/
  • modules/pmse_Inbox/engine/PMSEElements/
  • modules/pmse_Inbox/engine/PMSEHandlers/
  • modules/pmse_Inbox/engine/PMSEPreProcessor/
  • modules/pmse_Inbox/engine/wrappers/
  • modules/pmse_Project/clients/base/api/
  • modules/pmse_Project/clients/base/api/wrappers/
  • modules/pmse_Project/clients/base/api/wrappers/PMSEObservers/

What this means is that virtually any class that is found by name in these directories can be instantiated and returned via the getPMSEObject() method. This also means that customizations to any of these classes can be easily implemented by creating a Custom version of the class under the appropriate ./custom directory path.

For example, to create a custom version of PMSEProjectWrapper, you would create a CustomPMSEProjectWrapper class at  ./custom/modules/pmse_Project/clients/base/api/wrappers/CustomPMSEProjectWrapper.php. This allows you to have full control of your instance of SugarBPM while giving you the flexibility to add any classes you want and consuming them through the factory.

./custom/modules/pmse_Project/clients/base/api/wrappers/CustomPMSEProjectWrapper.php

  <?php

require_once 'modules/pmse_Project/clients/base/api/wrappers/PMSEProjectWrapper.php';

class CustomPMSEProjectWrapper extends PMSEProjectWrapper
{
}

This can now be consumed by simply calling the getPMSEObject() method:

  <?php

// Set the process manager library namespace
use \Sugarcrm\Sugarcrm\ProcessManager\Factory;

// Get our wrapper
$wrapper = ProcessManager\Factory::getPMSEObject('PMSEProjectWrapper');

Getting a SugarBPM Element Object

Element objects in SugarBPM generally represent some part of the Process engine, and often time map to design elements. All Element objects can be found in the modules/pmse_Inbox/engine/PMSEElements/ directory.

Getting a SugarBPM Element object can be done easily by calling the getElement() method of the Process Manager Factory:

  <?php

use \Sugarcrm\Sugarcrm\ProcessManager\Factory;

// Get a default PMSEElement object
$element = ProcessManager\Factory::getElement();

To get a specific object, you can pass the object name to the getElement() method:

  $activity = ProcessManager\Factory::getElement('PMSEActivity');

Alternatively, you can remove the PMSE prefix and call the method with just the element name:

  $gateway = ProcessManager\Factory::getElement('Gateway');

To create a custom SugarBPM Element, you can simply create a new file at ./custom/modules/pmse_Inbox/engine/PMSEElements/ where the file name is prefixed with Custom.

./custom/modules/pmse_Inbox/engine/PMSEElements/CustomPMSEScriptTask.php

  <?php

require_once 'modules/pmse_Inbox/engine/PMSEElements/PMSEScriptTask.php';

use Sugarcrm\Sugarcrm\ProcessManager;

class CustomPMSEScriptTask extends PMSEScriptTask
{
}

Note: All SugarBPM Element classes must implement the PMSERunnable interface. Failure to implement this interface will result in an exception when using the Factory to get an Element object.

To get a custom ScriptTask object via the Factory, just call getElement():

$obj = ProcessManager\Factory::getElement('ScriptTask');

To make your own custom widget element, you can implement the PMSERunnable interface:

./custom/modules/pmse_Inbox/engine/PMSEElements/CustomPMSEWidget.php

  <?php

require_once 'modules/pmse_Inbox/engine/PMSEElements/PMSERunnable.php';

use Sugarcrm\Sugarcrm\ProcessManager;

class CustomPMSEWidget implements PMSERunnable
{
}

To get a Widget object via the Factory, just call getElement():

  $obj = ProcessManager\Factory::getElement('Widget');

Getting and Using a Process Manager Field Evaluator Object

Process Manager Field Evaluator objects determine if a field on a record has changed and if a field on a record is empty according to the field type. The purpose of these classes can grow to include more functionality in the future.

Getting a field evaluator object is also done through the Factory:

  <?php

use \Sugarcrm\Sugarcrm\ProcessManager\Factory;

// Get a Datetime Evaluator. Type can be date, time, datetime or datetimecombo
$eval = ProcessManager\Factory::getFieldEvaluator(['type'  => 'date']);

A more common way of getting a field evaluator object is to pass the field definitions for a field on a SugarBean into the Factory:

  $eval = ProcessManager\Factory::getFieldEvaluator($bean ->field_defs[$field]);
$eval = ProcessManager\Factory::getFieldEvaluator($bean ->field_defs[$field]);

Once the evaluator object is fetched, you may initialize it with properties:

  /*
 This is commonly used for field change evaluations
 */

// $bean is a SugarBean object
// $field is the name of the field being evaluated
// $data is an array of data that is used in the evaluation
$eval ->init($bean, $field, $data);

// Has the field changed?
$changed  = $eval->hasChanged();
 

Or you can set properties onto the evaluator:

  /*
 This is commonly used for empty evaluations
 */

// Set the bean onto the evaluator
$eval->setBean($bean);

// And set the name onto the evaluator
$eval->setName($field);

// Are we empty?
$isEmpty  = $eval->isEmpty();

Creating and consuming a custom field evaluator is as easy as creating a custom class and extending the Base class:

./custom/src/ProcessManager/Field/Evaluator/CustomWidget.php

  <?php

namespace Sugarcrm\Sugarcrm\ProcessManager\Field\Evaluator;

/**
 * Custom widget field evaluator
 *  @package ProcessManager
 */
class CustomWidget extends Base
{
}

 

Alternatively, you can create a custom evaluator that extends the AbstractEvaluator parent class if you implement the EvaluatorInterface interface.

./custom/src/ProcessManager/Field/Evaluator/CustomWidget.php

  <?php

namespace Sugarcrm\Sugarcrm\ProcessManager\Field\Evaluator;

/**
 * Custom widget field evaluator
 *  @package ProcessManager
 */
class CustomWidget extends AbstractEvaluator  implements EvaluatorInterface
{
}

And at this point, you can use the Factory to get your widget object:

  // Get a custom widget evaluator object
$eval  = ProcessManager\Factory::getFieldEvaluator(['type'  => 'widget']);

Getting an Exception Object with Logging

Getting a SugarBPM exception with logging is as easy as calling a single Factory method. Currently, the Process Manager library supports the following exception types:

  • DateTime
  • Execution
  • InvalidData
  • NotAuthorized
  • Runtime

As a fallback, the Base exception can be used. All exceptions log their message to the PMSE log when created, and all exceptions implement ExceptionInterface.

The most basic way to get a ProcessManager exception is to call the getException() method on the Factory with no arguments:

  <?php

use \Sugarcrm\Sugarcrm\ProcessManager\Factory;

// Get a BaseException object, with a default message
// of 'An unknown Process Manager exception had occurred'
$exception = ProcessManager\Factory::getException();

 

To get a particular type of exception, or to set your message, you can add the first two arguments:

  if ($error) {
     throw ProcessManager\Factory::getException('InvalidData', $error);
}

It is possible to set an exception code, as well as a previous exception, when creating an Exception object:

  // Assume $previous is an ExecutionException
throw ProcessManager\Factory ::getException('Runtime', 'Cannot carry out the action', 255, $previous);

Finally, to create your own custom exception classes, you must add a new custom file in the following path with the file named appropriately:

./custom/src/ProcessManager/Exception/CustomEvaluatorException.php

  <?php

namespace Sugarcrm\Sugarcrm\ProcessManager\Exception;

/**
 * Custom Evaluator exception
 *  @package ProcessManager
 */
class CustomEvaluatorException  extends BaseException  implements ExceptionInterface
{
}

And to consume this custom exception, call the Factory:

  $exception = ProcessManager\Factory::getException('Evaluator', 'Could not evaluate the expression');

Maintaining State Throughout a Process

The Registry class implements the RegistryInterface which exposes the following public methods:

  • set($key, $value, $override = false)
  • get($key, $default = null)
  • has($key)
  • drop($key)
  • getChanges($key)
  • reset()

To use the Registry class, simply use the namespace to get the singleton:

  <?php

use \Sugarcrm\Sugarcrm\ProcessManager\Registry;

$registry = Registry\Registry::getInstance();

To set a value into the registry, simply add it along with a key:

  $registry->set('reg_key', 'Hold on to this');

NOTE: To prevent named index collision, it is advisable to write indexes to contain a namespace indicator, such as a prefix.

  $registry->set('mykeyindex:reg_key', 'Hold on to this');

Once a value is set in the registry, it is immutable unless the set($key, $value, $override = false) method is called with the override argument set to true:

  // Sets the reg_key value
$registry->set('reg_key', 'Hold on to this');

// Since the registry key reg_key is already set, this will do nothing
$registry->set('reg_key', 'No wait!');

// To forcefully set it, add a true for the third argument
$registry->set('reg_key', 'No wait!', true);

When a key is changed on the registry, these changes are logged and can be inspected using getChanges():

  // Set and reset a value on the registry
$registry->set('key', 'Foo');
$registry->set('key', 'Bar', true);
$registry->set('key', 'Baz', true);

// Get the changes for this key
$changes  = $registry ->getChanges('key');

/*
$changes will be:
 array(
     0 => array(
         'from' => 'Foo',
         'to' => 'Bar',
     ),
     1 => array(
         'from' => 'Bar',
         'to' => 'Baz',
     ),
 */

To get the value of the key you just set, call the get() method:

  $value = $registry->get('reg_key');

To get a value of a key with a default, you can pass the default value as the second argument to get():

  // Will return either the value for attendee_count or 0
$count  = $registry->get('attendee_count', 0);

To see if the registry currently holds a value, call the has() method:

  if ($registry->has('field_list')) {
    // Do something here
}

To remove a value from the registry, use the drop() method. This will add an entry to the changelog, provided the key was already set.

  $registry->drop('key');

Finally, to clear the entire registry of all values and changelog entries, use the reset() method.

  $registry->reset();

Note: This is very destructive. Please use with caution because you can purge settings that you didn't own.