Date: Fri, 29 Mar 2024 10:23:43 +0000 (UTC) Message-ID: <839745018.7835.1711707823428@ip-10-10-7-29.ec2.internal> Subject: Exported From Confluence MIME-Version: 1.0 Content-Type: multipart/related; boundary="----=_Part_7834_656979830.1711707823427" ------=_Part_7834_656979830.1711707823427 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Content-Location: file:///C:/exported.html
This document is intended to provide guidance to anyone writing code aga= inst COmanage Registry PE (v5.0.0 or later), including plugin developers. I= t describes various facilities developed for COmanage specifically, to help= make it easier to write DRY code.
Before continuing, be sure to be familiar with the following:
Registry PE is targeting PHP 8+. In particular:
Registry PE generally adopts PSR-12, with some variations. Where discre= pancies exist between this section and the rest of this document, this sect= ion controls and the conflict should be assumed to be a legacy artifact tha= t has not been updated.
In general, CakePHP Coding Conventio= ns should also be followed, except where they conflict with guidance in= this document.
For developers already familiar with the v4 and earlier coding style, th= is section highlights the changes:
int
and integ=
er
were used interchangeable.XXX PHP_CodeSniffer
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 mode= ls added to the application). Build a proposed functional design around the= data model changes. For code that will be contributed to the project, revi= ew these designs with the development group before beginning any meaningful= coding, since code that does not align with the project's direction will n= ot be accepted.
Schema management is handled by Do=
ctrine DBAL, using a Registry specific JSON schema file processed by
columnLibrary
provides default definitions for commonl=
y used attributes. These defaults will be used when the table defines a col=
umn name with the same name as the library definition. All library values a=
re inherited by default, it is then only necessary to explicitly define the=
ones that should be changed.timestamps
is set to false in the table defini=
tion.changelog
i=
s set to false in the table definition.sourced
to tr=
ue will insert the necessary foreign key and index.mvea
t=
o a list of parent tables will insert the necessary foreign keys and indexe=
s.Note that since JSON files inexplicably can't have comments, the key
"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 usi=
ng 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 impl=
emented 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 ru=
le for that model (for example: AR-ApiUser-3
). Global rules ar=
e referred to as General Model Rules, and are labeled AR-GMR=
-#
.
Wherever possible, log entries (to the rules
level) should =
be generated when an Application Rule is applied.
In general, Application Rules are not configurable.
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 field=
Map
. (The displayField
is used when Transmogrification =
is running.)
In some cases, a custom mapping function is required to calculate the ta=
rget table value. In some of these cases, results from an earlier table sho=
uld be cached so later mapping actions can quickly find earlier values. Thi=
s 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 bool=
eans
entry.
XXX
XXX
Registry builds various utilities on top of the Cake framework.
Unlike in v4 and earlier, API transactions are handled entirely by dedic=
ated controllers. The standard Registry model level API is implemented by <=
code>ApiV2Controller, other APIs are implemented in plugins. As a re=
sult, controller specific logic (such as overriding beforeFilter) will not automatically apply to APIs. Model specific logi=
c that needs to apply to both the UI and API should be defined in the model=
(table) through the use of Traits or other similar techniques, and then re=
ferenced generically from the calling controllers.
XXX
In general, the previous documentation on Changelog Behavior applies, though not all features = are implemented yet.
delete
requests and c=
onverts them to updates, beforeDelete
and afterDelete callbacks should not be used.
ChangelogBehaviorTrait<=
/code>.
XXX
delete
requests and c=
onverts them to updates, beforeDelete
and afterDelete callbacks should not be used.
ChangelogBehaviorTrait<=
/code>, which implements afterSave
. As such, Tables should not=
implement their own afterSave
, but should implement loc=
alAfterSave
(with the same function signature as afterSave). localAfterSave
will not be called when archived records =
are written to the database.
XXX
When defining functions, use parameter type, return types, and default v= alues. When the ID of the current Model is a parameter, it should be first = in the list.
public = myFunction(int $id, string $label, bool $colorful=3Dfalse): string { ... }<= /pre>
When calling functions, use parameter names wherever possible.
$s =3D = $this->myFunction(id: $entity->id, label: __d('information', 'my.labe= l'), colorful: true);
Avoid the use of configuration arrays (though Cake still makes heavy use= of these).
public = badExample($id, $options=3D[]);
When localizing text strings, use the table name and/or field name as is= whenever possible.
Normally, a relation can simply be defined using something like
$this-&= gt;belongsTo('Types')
which implies the current table has a columns type_id
. Howe=
ver, sometimes it is necessary or desirable to use a different foreign key =
name, such as default_type_id
. This can be accomplished with s=
omething like
$this-&= gt;belongsTo('Types')->setForeignKey('default_type_id');
Registry's foreign key checks further require a property to be set so th=
at ruleValidateCO
can properly validate foreign keys at run ti=
me. This can be accomplished by setting a property with the name of the for=
eign key without the _id
:
$this-&= gt;belongsTo('Type')->setForeignKey('default_type_id')->setProperty('= default_type');
Many models can be ordered, eg Provisioning Targets. In order to leverag= e common utility code:
ordr
, spelled with out the "e". (This is because order
is a reser=
ved keyword in MySQL.)ordr
if none is provided =
when a new entity is saved.
ordr
value. So, for example, when a new Provisioning=
Target is added, max(ordr)
is determined for all Provisioning=
Targets within the same CO, while for Enrollment Flow Steps max(ordr=
)
is determined for all Enrollment Flow Steps within the same Enroll=
ment Flow.All timestamps are stored in the database in UTC (AR-GMR-4).
To automatically convert to UTC on save, the table should load Tim=
ezoneBehavior
. FieldHelper::control()
will convert from=
UTC on rendering, as will the Standard index.php
when c=
olumns.inc
sets the field type to datetime
.
There is no timezone conversion for the REST API.
See also: Registry Time= zones
Common code used to be placed in AppModel
, which led to a l=
arge and complicated pile of code. In general, common functionality is now =
implemented using traits.
XXX
Models should record History at appropriate points to facilitate adminis=
trator review of actions affecting a Person record. HistoryTrait offers utility functions to simplify recording history. It is also possi=
ble to use
HistoryRecordsTable
directly, though it may be more=
complicated to do so.
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, g=
enerate links, and other similar purposes.
After adding or editing an entity, different models may have different u= ser experiences for where to go next. The selection of a redirect target ca= n be controlled by setting a Redirect Goal. Currently support= ed Redirect Goals are
index
: Redirect to the index for the model, filtered by th=
e Primary LinkprimaryLink
: Redirect to the Primary Link entityself
: Re-render the same formPrimary Links can be declared to Plugin models (for example, the a Plugi=
n defines secondary models to a Primary Plugin model) using the notation $this->setPrimaryLink=
('CoreAssigner.format_assigner_id');
will declare the primary link t=
o be to CoreAssigner.FormatAssigners::id
.
Cake supports two main ways for referencing another model (table) from w= ithin a model (table) or controller. The first is via the model relation:= p>
// eg, i= n GroupMembersTable.php: $person =3D $this->People->get($entity->person_id);
The second is via the TableLocat=
or
class. The TableLocator is directly available in a controller=
, or can be accessed using the LocatorAwareTrait
it a Model.=
p>
// In a = controller $People =3D $this->getTableLocator()->get('People'); // In a model class MyTable extends Table { use \Cake\ORM\Locator\LocatorAwareTrait; public function doSomething() { $People =3D $this->getTableLocator()->get("People"); } }
In general, either approach is acceptable. The first approach is usually= simpler and more compact, however when a long chain of relations is requir= ed to get to the desired table, the second approach may be preferable. When= there is no directly relation, the second approach is required.
In general, model code should obtain the current CO via parameters passe=
d to the functions it implements, either directly (when there is no other p=
arameter that implies a CO) or indirectly (when another parameter, such as =
$personId
, can be used to calculate the CO). The function PrimaryLinkTrait
=
) can be helpful.
In rare cases, it may be necessary to determine the CO by other means, f=
or example in order to adjust validation rules based on the current CO. Mod=
els can setAcceptsCoId
on their table (again via Primary=
LinkTrait
), 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.
In general, avoid using joins unless required for performance reasons. J=
oins make the code harder to read, require special annotations, may interac=
t poorly with ChangelogBehavior, and require spe=
cial handling when the database configuration has quoteIdentifier=
s
enabled (which is required for MySQL).
XXX
It is possible to append a custom string (such as "Primary Link") to a f=
ield in the index view on a per-record basis using the append
=
directive. The value is a function implemented on the entity. For example,<=
/p>
$indexC= olumns =3D [ 'type_id' =3D> [ 'type' =3D> 'fk', 'append' =3D> 'primaryLabel' ] ]
when rendering the field type for the index of names=
em>, $name=E2=86=92primaryLink(): string
will be called and th=
e returned string will be appended with a comma to the string for the curre=
nt value of the type foreign key. The result will be something like "=
Official, Primary Name
".
XXX
By default, the display field used for a model is whatever is set using =
Cake's setDisplayField
. However, tables can implement model-sp=
ecific display logic by implementing generateDisplayField($entity): s=
tring
.