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
- Evaluator/
- 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.