Background
This document is intended to provide guidance to anyone writing code against COmanage Registry PE (v5.0.0 or later), including plugin developers. It describes various facilities developed for COmanage specifically, to help make it easier to write DRY code.
Prerequisites
Before continuing, be sure to be familiar with the following:
- PHP, and in particular newer PHP features such as
- CakePHP v4 and the MVC design pattern
- The Registry Data Model (and the Technical Manual in general)
PHP 8
Registry PE is targeting PHP 8+. In particular:
- Function calls should be typed, including the return type.
Adding New Models
Data Model and Functional Design
Before writing any code, start by proposing changes to the existing data model (which may include defining new tables that will map to the new models added to the application). Build a proposed functional design around the data model changes. For code that will be contributed to the project, review these designs with the development group before beginning any meaningful coding, since code that does not align with the project's direction will not be accepted.
schema.json
Schema management is handled by Doctrine DBAL, using a Registry specific JSON schema file processed by DatabaseCommand
. The format of the schema file is fairly self documenting, but note the following:
- The
columnLibrary
provides default definitions for commonly used attributes. These defaults will be used when the table defines a column name with the same name as the library definition. All library values are inherited by default, it is then only necessary to explicitly define the ones that should be changed. - Index names must be explicitly specified (as opposed to autogenerated) so that if the index definition changes DBAL can recompute the index. (This is much more efficient than the previous ADOdb based system, which rebuilt all indexes on the table any time the table was changed in any way.)
- In general, do not define unique constraints on indexes, as they will conflict with Changelog Behavior. Uniqueness can be enforced using Application
Rules.
- In general, do not define unique constraints on indexes, as they will conflict with Changelog Behavior. Uniqueness can be enforced using Application
- Cake's Timestamp fields will be automatically inserted, unless
timestamps
is set to false in the table definition. - Fields for Changelog Behavior will be automatically inserted, unless
changelog
is set to false in the table definition. - For attributes that can be created via Registry Pipelines, setting
sourced
to true will insert the necessary foreign key and index. - For Multi-Valued Entity Attributes (MVEAs), setting
mvea
to a list of parent tables will insert the necessary foreign keys and indexes.
Note that since JSON files inexplicably can't have comments, the key comment
is reserved in all contexts except the list of column definitions to be used for comments.
Application Rules
"Implicit logic" must be documented in the form of Application Rules.
Application Rules are typically enforced using Cake's Application Rules, which are applied to a table using the table's buildRules()
function. By convention, COmanage rules are named ruleSomethingOrOther()
, and are defined in the table they apply to. Rules common to multiple tables should be implemented in RulesTrait
. Global rules that apply to all tables are implemented in the RuleBuilderEventListener
.
Application Rules must be labeled in a comment adjacent to the code that enforces them using the form AR-Model-#
, that is the string "AR-", the camel cased singular model name, a dash, and the number of the rule for that model (for example: AR-ApiUser-3
). Global rules are referred to as General Model Rules, and are labeled AR-GMR-#
.
In general, Application Rules are not configurable.
Transmogrification
For migration of Registry tables from v4 to v5, appropriate support for migrating existing data must be added to TransmogrificationCommand
.
Each table must be added to $tables
in the same order as schema.json
(ie: to correctly sequence the population of primary keys). By default, fields are mapped 1-1 unless configured via fieldMap
. (The displayField
is used when Transmogrification is running.)
In some cases, a custom mapping function is required to calculate the target table value. In some of these cases, results from an earlier table should be cached so later mapping actions can quickly find earlier values. This is accomplished with the cache
entry.
If a table was not previously Changelog enabled but is Changelog enabled in v5, the key addChangelog
must be set to true
.
Boolean fields must currently be explicitly identified in the booleans
entry.
Logging
XXX
Testing
XXX
Documentation
- Application Rules must be documented as described above.
- Functional documentation should be added to the Technical Manual.
- The Data Model must be updated.
- The REST API documentation must be updated, if needed.
Leveraging DRY Patterns
Registry builds various utilities on top of the Cake framework.
Authorization (RegistryAuth Component)
XXX
Behaviors
Changelog Behavior
XXX
Log Behavior
XXX
Enumerations
XXX
Localizations
When localizing text strings, use the table name and/or field name as is whenever possible.
Traits
Common code used to be placed in AppModel
, which led to a large and complicated pile of code. In general, common functionality is now implemented using traits.
AutoViewVars Trait
XXX
PrimaryLink Trait
The Primary Link of a table is the foreign key to the most significant parent object, typically co_id
or person_id
. The Primary Link is used to automatically determine permissions, generate links, and other similar purposes.
Redirect Goals
After adding or editing an entity, different models may have different user experiences for where to go next. The selection of a redirect target can be controlled by setting a Redirect Goal. Currently supported Redirect Goals are
index
: Redirect to the index for the model, filtered by the Primary LinkprimaryLink
: Redirect to the Primary Link entityself
: Re-render the same form
Models
Determining the Current CO
In general, model code should obtain the current CO via parameters passed to the functions it implements, either directly (when there is no other parameter that implies a CO) or indirectly (when another parameter, such as $personId
, can be used to calculate the CO). The function findCoForRecord
(implemented in PrimaryLinkTrait
) can be helpful.
In rare cases, it may be necessary to determine the CO by other means, for example in order to adjust validation rules based on the current CO. Models can setAcceptsCoId
on their table (again via PrimaryLinkTrait
), and AppController
will then provide the CO to the table as part of setCO
. Note this currently works only for the primary table of the request and its immediate relations.
Validation and Application Rules
XXX
Views
columns.inc
Appending a Custom String
It is possible to append a custom string (such as "Primary Link") to a field in the index view on a per-record basis using the append
directive. The value is a function implemented on the entity. For example,
$indexColumns = [ 'type_id' => [ 'type' => 'fk', 'append' => 'primaryLabel' ] ]
when rendering the field type for the index of names, $name→primaryLink(): string
will be called and the returned string will be appended with a comma to the string for the current value of the type foreign key. The result will be something like "Official, Primary Name
".
fields.inc and FieldHelper
XXX
Custom Display Fields
By default, the display field used for a model is whatever is set using Cake's setDisplayField
. However, tables can implement model-specific display logic by implementing generateDisplayField($entity): string
.