This guide is here to help developers follow best practices when working with Declarative and Serializable Metadata in Sugar. We've identified some common mistakes developers make and put together practical tips on how to avoid them using proven best practices.
General Guidelines for Declarative Metadata:
- Static Definitions: Always use static and explicit values rather than computed ones.
- Avoid Direct Database Calls: Metadata should not perform direct database queries
Avoid Using $GLOBALS Array
Metadata definitions should directly declare configurations explicitly instead of relying on global variables.
Problematic Example:
$GLOBALS["dictionary"]["MyModule"] = [/* ... */];
Recommended Approach: Use the local $dictionary variable directly:
$dictionary["MyModule"] = [/* ... */];
Eliminate Imperative PHP Code
Avoid embedding business logic or conditional statements directly in metadata files.
Problematic Example:
$label_prefix = 'QUESTIONS_CONFIG'; $mod_strings["LBL_{$label_prefix}_" . strtoupper("view")] = 'My Configuration Panel';
Recommended Approach: Define constants explicitly without dynamic runtime manipulation:
$mod_strings['LBL_QUESTIONS_CONFIG_VIEW'] = 'My Configuration Panel';
Remove Entry Point Validations
Entry point checks are unnecessary in metadata definitions, as these files should be simple data files..
Problematic Example:
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
Recommended Approach: Simply omit such checks from metadata entirely.
Avoid Conditional Class Checks and Dynamic Includes
Don't use conditional logic or dynamic includes in metadata definitions.
Problematic Example:
if (!class_exists('VardefManager')) { require_once 'include/SugarObjects/VardefManager.php'; } VardefManager::createVardef('MyModule','MyModule', ['basic','assignable']);
Recommended Approach: Rely on autoloading and straightforward declarations:
VardefManager::createVardef('MyModule','MyModule', ['basic','assignable']);
Note: we plan to introduce a better way of extending vardefs from module templates. It will be more declarative and should avoid VardefManager::createVardef calls inside the vardefs files.
Logic hooks
Separate definition from implementation where Metadata declares where Logic Hook should be triggered from.
Problematic Example:
> head -n8 custom/modules/logic_hooks.php <?php class InstallationReviewHistory { static $already_ran = false; private $update_project_relations; public function createRecordFromX($bean, $event, $arguments) {
Recommended Approach: custom/modules/logic_hooks.php
must contain the Logic Hook definition and not the class itself
<?php
$hook_version = 1;
$hook_array = Array();
$hook_array['after_session_start'] = Array();
$hook_array['after_session_start'][] = Array(
//Processing index. For sorting the array.
1,
//Label. A string value to identify the hook.
'after_session_start example',
//The PHP file where your class is located.
'custom/modules/application_hooks_class.php',
//The class the method is in.
'application_hooks_class',
//The method to call.
'after_session_start_method'
);
Metadata files
(view defs, vardefs, etc) that are supposed to have specific code defined as arrays should not have any function calls in them or any other logic like string concatenation or loops.
Problematic Example:
foreach (["after_retrieve", "after_fetch_query", "after_entry_point", "CallsApiHelper_formatForApi_after", "MeetingsApiHelper_formatForApi_after"] as $hkNameToInit) { ... foreach ($aHooks as &$aHook) { ... foreach ($hook_array as $sEvent => &$aHooks) {
Views/Layouts definitions
Compiled definitions should only contain array definitions.
Problematic Example: existing code in custom/application/Ext/clients/base/layouts/header/header.ext.php
$u2af72f100c356273d46284f6fd1dfc08 = new \Sugarcrm\Sugarcrm\custom\MyCompany\Module1\Libraries\PHP\Classes\Sugar\Version($GLOBALS["sugar_version"]); --- $u2af72f100c356273d46284f6fd1dfc08 = new \Sugarcrm\Sugarcrm\custom\MyCompany\Module2\Libraries\PHP\Classes\Sugar\Version($GLOBALS["sugar_version"]); --- $u2af72f100c356273d46284f6fd1dfc08 = new \Sugarcrm\Sugarcrm\custom\MyCompany\Module1\Libraries\PHP\Classes\Sugar\Version($GLOBALS["sugar_version"]); --- $version = new \Sugarcrm\Sugarcrm\custom\MyCompany\Module3\Libraries\PHP\Classes\Sugar\Version($GLOBALS['sugar_version']); --- $version = new \Sugarcrm\Sugarcrm\custom\MyCompany\Module2\Libraries\PHP\Classes\Sugar\Version($GLOBALS['sugar_version']); --- $version = new \Sugarcrm\Sugarcrm\custom\MyCompany\Module1\Libraries\PHP\Classes\Sugar\Version($GLOBALS['sugar_version']); --- if (\SugarAutoloader::fileExists('custom/src/MyCompany/Module3/Libraries/PHP/Classes/Sugar/Version.php') && class_exists('\\Sugarcrm\\Sugarcrm\\custom\\MyCompany\\Module3\\Libraries\\PHP\\Classes\\Sugar\\Version') && isset($GLOBALS['sugar_version'])) { --- if (\SugarAutoloader::fileExists('custom/src/MyCompany/Module2/Libraries/PHP/Classes/Sugar/Version.php') && class_exists('\\Sugarcrm\\Sugarcrm\\custom\\MyCompany\\Module2\\Libraries\\PHP\\Classes\\Sugar\\Version') && isset($GLOBALS['sugar_version'])) { --- if (\SugarAutoloader::fileExists('custom/src/MyCompany/Module1/Libraries/PHP/Classes/Sugar/Version.php') && class_exists('\\Sugarcrm\\Sugarcrm\\custom\\MyCompany\\Module1\\Libraries\\PHP\\Classes\\Sugar\\Version') && isset($GLOBALS['sugar_version'])) { --- if (\SugarAutoloader::fileExists("custom/src/MyCompany/CallCenter/Libraries/PHP/Classes/Sugar/Version.php") && class_exists("\\Sugarcrm\\Sugarcrm\\custom\\MyCompany\\CallCenter\\Libraries\\PHP\\Classes\\Sugar\\Version") && isset($GLOBALS["sugar_version"])) { --- if (\SugarAutoloader::fileExists("custom/src/MyCompany/Module2/Libraries/PHP/Classes/Sugar/Version.php") && class_exists("\\Sugarcrm\\Sugarcrm\\custom\\MyCompany\\Module2\\Libraries\\PHP\\Classes\\Sugar\\Version") && isset($GLOBALS["sugar_version"])) {
Problematic Example 2: existing code in custom/modules/Contacts/Ext/clients/base/views/record/record.ext.php
$GLOBALS['log']->fatal('After Salesfusion Install'); --- $GLOBALS['log']->fatal('Install Marker 1'); --- $GLOBALS['log']->fatal('Install Marker 2'); --- $GLOBALS['log']->fatal('Install Marker 3: '.$k); --- $GLOBALS['log']->fatal('Install Marker 4'); --- $GLOBALS['log']->fatal('Install Marker 5'); --- $GLOBALS['log']->fatal('Starting Extension code for Contacts.... '); --- $GLOBALS['log']->fatal("Implicit for Module1: Error - " . $e->getMessage()); --- $GLOBALS['log']->info('After My MLP Install'); --- $GLOBALS['log']->info('Install Marker 1'); --- $GLOBALS['log']->info('Install Marker 2'); --- $GLOBALS['log']->info('Install Marker 3: ' . $k); --- $GLOBALS['log']->info('Install Marker 3: '.$k); --- $GLOBALS['log']->info('Install Marker 4'); --- $GLOBALS['log']->info('Install Marker 5'); --- $GLOBALS['log']->info('Starting Extension code for Contacts.... '); --- $GLOBALS['log']->info('Starting Extension code for Contacts.... '); $module = 'Contacts'; $addAdhocButton = array( 'type' => 'rowaction', 'event' => 'button:my_adhoc:click', 'tooltip' => 'LBL_MY_ADHOC_MEET_TOOLTIP', 'css_class' => 'btn', 'showOn' => 'view', 'icon' => 'fa-comment', ); $addCreateMeetingButton = array( 'type' => 'rowaction', 'event' => 'button:my_create_meeting:click', 'tooltip' => 'LBL_MY_CREATE_MEET_TOOLTIP', 'css_class' => 'btn', 'showOn' => 'view', 'icon' => 'fa-calendar', ); if (!isset($viewdefs[$module]['base']['view']['record']['buttons'])) { $GLOBALS['log']->info('Install Marker 1'); require_once('clients/base/views/record/record.php'); $viewdefs[$module]['base']['view']['record']['buttons'] = $viewdefs['base']['view']['record']['buttons']; } array_unshift($viewdefs[$module]['base']['view']['record']['buttons'],$addAdhocButton,$addCreateMeetingButton); $GLOBALS['log']->info('After My_Component Install');
Recommended Approach: metadata definition only:
<?php $viewdefs['base']['layout']['header'] = [ 'components' => [ [ 'layout' => 'module-list', ], [ 'layout' => 'quicksearch', ], [ 'view' => 'notifications', ], [ 'view' => 'profileactions', ], [ 'view' => 'quickcreate', ], ], 'last_state' => [ 'id' => 'app-header', 'defaults' => [ 'last-home' => 'dashboard', ], ], ];