This documentation currently applies to MR7 and later, and is expected to evolve with future releases.

Management of CakePHP plugins has changed significantly since Cake 2, largely due to the use of PHP Autoloading in Cake 4. The least bad way to handle this in a production environment is to install plugins via composer, though such procedures are not yet formalized for Registry PE. This document currently describes how to write plugins that will ship with Registry (either core or available).

In addition, Registry Plugins have been redesigned to address various long standing problems. All plugins are expected to be instantiated, with improved common libraries to simplify the design of new plugin types. Instantiations now refer to plugins via Plugin.Model notation, creating a generalized solution for polymorphic plugins (plugins that can implement more than one plugin type) as well as multiple entry points (so a single plugin can easily provide multiple functions).

Terminology

  • Activation: The act of enabling an Available Plugin. Only a Platform Administrator can activate an Available Plugin.
  • Available Plugin: A Registry Plugin that is shipped with the application but is not enabled by default.
  • Cake Plugin: An application within an application.
  • Core Plugin: A Registry Plugin that is shipped with the application and cannot be disabled.
  • Entry Point Model: Within a Registry Plugin, a Model that implements a Plugin Type.
  • Installation: The act of adding a Cake Plugin to the Registry application filesystem.
  • Instantiation: The act of creating a new configuration with a CO that leverages a Registry Plugin.
  • Local Plugin: A Registry Plugin added by a deployer.
  • Pluggable Model: The core Registry model that understands Registry Plugins of a given type. Example include ExternalIdentitySource and Report.
  • Plugin: The unqualified term "Plugin" generally means "Registry Plugin".
  • Plugin Type: The type of a plugin, which is derivable from the Pluggable Model via "underscore" inflection. So ExternalIdentitySource plugins are of type external_identity_source. (underscore is used rather than dasherize so that the type can be easily converted back via classify or camelize.)
  • Registry Plugin: A Cake Plugin with additional syntax or requirements.

Creating a New Plugin

Bake

The easiest way to create the structure for the new Plugin is with Cake's bake command.

While there are no longer technical requirements around the construction of a Plugin's name, it is still advisable for the Plugin to be named in a way that describes its primary purpose, such as SqlSource or NightlyReport.

Pick an appropriate location for the plugin: Core Plugins should go in app/plugins, Available Plugins should go in app/availableplugins, and Local Plugins should go in local/plugins.

$ cd $SRC/app
$ ./bin/cake bake plugin MyPlugin
1. /src/comanage/git/registry-pe/app/plugins/
2. /src/comanage/git/registry-pe/app/availableplugins/
3. /src/comanage/git/registry-pe/app/../local//plugins/
Choose a plugin path from the paths above.
> 2

Allow composer.json to be overwritten, this is how the autoloader paths will be updated.

Modifying composer autoloader

File `/src/comanage/git/registry-pe/app/composer.json` exists
Do you want to overwrite? (y/n/a/q) y

It's not obvious from the command output, but app/src/Application.php will also be updated to load the plugin. This change is not needed (Registry will automatically load activated plugins) and should be reverted.

The default namespace for the Plugin will be the name of the plugin (MyPlugin in the above example). This will be recorded in composer.json and can be used in Plugin files (eg by declaring something like namespace MyPlugin\Model\Table;). This ensures Plugin code will not conflict with Registry (App  namespace) or Cake (Cake  namespace) code.


The bake command will not, by default, create additional directory structure for the plugin. See the documentation for more information.

plugin.json

Within the plugin directory, create the file src/config/plugin.json. This file contains a single JSON document, with the following top level members:

  • schema: The database schema for this plugin. This uses the same format as the main schema.json file, and will inherit definitions from that file's columnLibrary.
    • In general, a plugin-type specific foreign key column (eg: reports_id) is defined for each plugin type to simplify schema definition.
    • For information on applying the database schema, see Activating Plugins, below.
  • types: The Plugin Type(s) supported by this plugin. Within each Plugin Type, a list of Entry Point Models for that type implemented by the plugin.

Plugin Types

The following Plugin Types are supported:

PluginDescriptionAvailable SinceAdditional Requirements
assignerIdentifier Assignment Pluginsv5.0.0 MR10Identifier Assignment Plugins (PE)
enrollerEnrollment Flow Plugins
Enrollment Flow Plugins (PE)
jobJob Pluginsv5.0.0 MR8Job Plugins (PE)
provisionerProvisioner Pluginsv5.0.0 MR9Provisioner Plugins (PE)
reportReport Pluginsv5.0.0 MR7Report Plugins (PE)
serverServer Pluginsv5.0.0 MR9Server Plugins (PE)
sourceExternal Identity Source Pluginsv5.0.0 MR11External Identity Source Plugins (PE)
Example plugin.json
{
   "types": {
      "report": [
         "PersonStatusReports"
      ]
   },
   "schema": {
      "tables": {
         "person_status_reports": {
            "columns": {
               "id": {},
               "report_id": { "type": "integer", "foreignkey": { "table": "reports", "column": "id" }, "notnull": true },
               "status": {}
            },
            "indexes": {
               "person_status_reports_i1": { "columns": [ "report_id" ] }
            }
         }
      }
   }
}

Additional Naming Conventions

Registry Plugins that implement both the source and provisioner Plugin Types are referred to as Connectors.

Entry Point Models

Plugins must implement at least one Entry Point Model for each Plugin Type declared in the types section. Entry Point Models belongTo the Pluggable Model. For example, PersonStatusReports belongsTo Reports. Plugins may implement additional models beyond the Entry Point Model, these do not need to be registered in plugin.json. There are no naming requirements for Entry Point Models, but it is recommended that the name references the Plugin Type (and in many cases may be the same as or similar to the plugin name).

Requirements for Entry Point Models vary by Plugin Type (see Additional Requirements By Plugin Type, below).

See also: Declaring Primary Links to Plugins

Plugin Instantiation

A Plugin is instantiated when an administrator creates a new instance of a Pluggable Model. As part of this process, a skeletal instance of the Entry Point Model is created, populated only with a foreign key to the Pluggable Model (AR-Plugin-9). Validation and Application Rules are not executed when this record is created. The administrator is then redirected to the edit view for the Entry Point Model (/plugin/controller/edit/#).

If the Pluggable Model is deleted, the Entry Point Model associated with it is also deleted (AR-Plugin-10).

Plugins do not need to (and generally should not) implement add or delete actions for their Entry Point Models.

Localizations

Registry PE uses the standard Cake localization mechanisms. Following convention, plugins should use the underscored version of their name as the domain for translation.

# In $PLUGIN/resources/locales/en_US/my_plugin.po
msgid "welcome"
msgstr "Welcome to My Plugin"

# In some code
print __d('my_plugin', 'welcome');

Plugins can also take advantage of automatic field localization in views. Whereas normally the field name filename referenced in fields.inc would be localized via __d('field', 'filename'), if the plugin defines a msgid of field.filename in my_plugin.po, the plugin's localization of filename will be used when rendering the plugin's view templates. The same applies for controller localizations.

Standard Views

StandardController will automatically look for view templates columns.inc and fields.inc for index and add/edit/view operations, respectively.

Enumerations

Plugins can extend StandardEnum to define their own enumerations.

To localize the enumeration values, add entries to the localization file (defined above) following the naming convention enumeration.EnumName.key.

To reference enumerations as automatic view variables, prefix the enumeration with the plugin name.

// $plugin/src/Lib/Enum/WidgetColorEnum.php

namespace MyWidget\Lib\Enum;

use App\Lib\Enum\StandardEnum;

class WidgetColorEnum extends StandardEnum {
  const Purple   = 'P';
  const Fuschia  = 'F';
}

// $plugin/resources/locales/en_US/my_widget.po
msgid "enumeration.WidgetColorEnum.F"
msgstr "Fuschia"

msgid "enumeration.WidgetColorEnum.P"
msgstr "Purple"

// $plugin/src/Model/Table/WidgetsTable.php

use MyWidget\Lib\Enum\WidgetColorEnum;

public function initialize(array $config): void {
  ...

  $this->setAutoViewVars([
    // This will automatically populate the field "colors" in the view form with the
    // enumeration values
    'colors' => [
      'type' => 'enum',
      'class' => 'MyWidget.WidgetColorEnum'
    ]
  ]);
}

public function validationDefault(Validator $validator): Validator {
  ...

  $validator->add('type', [
    'color' => ['rule' => ['inList', WidgetColorEnuum::getConstValues()]]
  ]);
}

Types

When Plugins allow data to be typed in inbound messages (eg: in a JSON file or SQL view), the machine readable value of the Type should be used, not the Display Name (which is more likely to be changed) and not the type record id (which exposes internal mechanics to upstream systems). Pipelines will automatically map value to the foreign key necessary for maintaining internal database records.

Defining APIs

Model-specific APIs are enabled by default via REST API v2.

Plugins may additionally implement function-oriented APIs by defining additional routes in the Plugin's config/routes.php. By convention, Plugins should publish their APIs at /api/pluginmodel (resulting in URLs like https://server.org/registry-pe/api/pluginmodel). Versioning underneath this URL should be the next component, eg /api/pluginmodel/v1 unless implementing an API that imposes other requirements.

Notes for Committing Plugins

For Core and Available Plugins, be sure to commit the changes to composer.json, vendor/composer/autoload_psr4.php, and vendor/composer/autoload_static.php, but not Application.php.

Installing Plugins

Procedures for local plugins have not yet been defined (CFM-243).

Moving Plugins

If a Plugin is moved between directories (eg: from availableplugins to plugins) several steps are required:

  1. The autoload paths must be updated in composer.json (update both autoload and autoload-dev).
  2. Tell composer to dump the new autoload paths.
    1. composer dumpautoload
  3. Clear the caches.
    1. ./bin/cake cache clear_all
  4. Update the Plugin Registry, as described in Activating Plugins, below.

Activating Plugins

Plugins are activated by a Platform Administrator. Within the COmanage CO, go to ConfigurationPlugins. Loading this page will refresh the Plugin Registry (AR-Plugin-11), the new Plugin should be available in the list of Plugins. Activate the Plugin via the Activate menu item. This will also apply the database schema defined in schema.json.  The database schema can manually be applied via the Apply Database Schema menu item.

A Plugin cannot be suspended if it has at least one Entry Point Model that is in use by a Configuration model (ie: referenced by a Pluggable Model that is categorized as Configuration). (AR-Plugin-8)

  • No labels