-
Notifications
You must be signed in to change notification settings - Fork 56
Entity Templates
(This documentation is a draft/WIP.)
In CiviCRM, an entity is a persistent data-type. Entities are defined by their schema; then ordinarily stored in MySQL; and finally accessed with various tools (like "DAO", "BAO", "APIv3", "APIv4", "Search Kit", and "Form Builder").
As of May-June 2024 (CiviCRM v5.74-5.75; dev/core#4999), major elements of the entity framework have changed -- and we now have two distinctive designs. We can call these Entity Framework v1 (EFv1) and Entity Framework v2 (EFv2).
The two versions have a high degree of runtime interoperability, enabling you to mix-and-match versions. They differ mostly in the file-layout and implementation.
In civix
v24.09+, all templates and commands will generate code for EFv2. It will no longer generate EFv1. It can convert existing entities from EFv1 to EFv2, although this is discretionary.
This page aims to describe key differences and any major trade-offs.
The essential difference is the number of files and the heaviness of the code-generation.
Entity Framework v1 (EFv1) | Entity Framework v2 (EFv2) | |
---|---|---|
Design Summary | More files. More duplicate information. More generated code. |
Fewer files. Less duplicate information. Less generated code. |
Adoption | CiviCRM <= 5.73 Civix <= 23.12
|
CiviCRM >= 5.74 Civix >= 24.09
|
Compatibility | CiviCRM 5.x (roughly; varies somewhat) |
CiviCRM >= 5.45
|
EFv1 and EFv2 use many of the same concepts -- which simplifies migration and enables interoperability. However, the changes are deep - and we should examine them from multiple angles.
Browsing the files gives a quick overview of what's changed:
Entity Framework v1 (EFv1) | Entity Framework v2 (EFv2) | Comment |
---|---|---|
info.xml |
info.xml |
Changed <upgrader> and <mixin>
|
my_extension.civix.php |
my_extension.civix.php |
Changed class-loading |
xml/schema/MyEntity.xml xml/schema/MyEntity.entityType.php
|
schema/MyEntity.entityType.php |
Changed file format |
CRM/MyExtension/Upgrader.php |
CRM/MyExtension/Upgrader.php |
Unchanged (mostly) |
CRM/MyExtension/BAO/MyEntity.php |
CRM/MyExtension/BAO/MyEntity.php |
Unchanged |
CRM/MyExtension/DAO/MyEntity.php |
CRM/MyExtension/DAO/MyEntity.php |
Complete rewrite (heavy vs light) |
Civi/Api4/MyEntity.php |
Civi/Api4/MyEntity.php |
Unchanged |
api/v3/MyEntity.php |
api/v3/MyEntity.php |
Unchanged |
sql/auto_install.sql sql/auto_uninstall.sql
|
(none) |
Complete removal |
mixin/entity-type-php@1.0.0.php |
mixin/entity-type-php@2.0.0.php mixin/lib/civimix-schema@5.XX.php
|
New helpers |
-
To make a new entity in EFv1, run 3 commands:
civix generate:upgrader civix generate:entity MyEntity civix generate:entity-boilerplate
-
To make a new entity in EFv2, run 1 commands:
civix generate:entity MyEntity
-
N.B. EFv2 removes the command
civix generate:entity-boilerplate
.
-
For installation, EFv2 uses dynamic SQL. Statements like
CREATE TABLE
are generated on-the-fly by the schema-helper (E::schema()
a.k.a.mixin/lib/civimix-schema@XXX.phar/src/SchemaHelper.php
). -
For installation, EFv1 uses static SQL files. When you run
generate:entity-boilerplate
, it reads fromxml/schema/*.xml
and createssql/auto_install.sql
(with statements likeCREATE TABLE
). -
N.B. Dynamic SQL is more attuned. CiviCRM databases may have different collations. EFv2 will always match the local collation. EFv1 will not.
-
N.B. Static SQL is more hackable. EFv1 allows you to hand-edit the SQL files. EFv2 does not.
-
EFv2 registers an upgrader via
info.xml
:<!-- EFv2 registration via info.xml --> <extension key="my_extension"> <upgrader>CiviMix\Schema\MyExtension\AutomaticUpgrader</upgrader> </extension>
The
AutomaticUpgrader
delegates responsibility to other files, i.e.- It calls
E::schema()->install()
, which readsschema/*.entityType.php
and automatically creates the SQL tables. - It calls your custom upgrade rules (
CRM_MyExtension_Upgrader
). Any functions likeupgrade_XXX()
will work the same as before.
- It calls
-
EFv1 directly registers your upgrader. It may use either
*.civix.php
orinfo.xml
:// EFv1 registration via *.civix.php function _my_extension_civix_civicrm_install() { _my_extension_civix_civicrm_config(); if ($upgrader = _my_extension_civix_upgrader()) { $upgrader->onInstall(); } } function _my_extension_civix_upgrader() { if (!file_exists(__DIR__ . '/CRM/MyExtension/Upgrader.php')) { return NULL; } else { return CRM_MyExtension_Upgrader_Base::instance(); } }
or
<!-- EFv1 registration via info.xml --> <extension key="my_extension"> <upgrader>CRM_MyExtension_Upgrader</upgrader> </extension>
-
N.B. EFv2's
AutomaticUpgrader
still calls your customCRM_MyExtension_Upgrader
. It should be compatible with ~99% of implementations, and it allows us to swap the SQL generator without patching downstream code.There can be exceptions. How do you recognize compatible and incompatible upgraders?
-
CRM_*_Upgrader
defines supported methods for overrides/customization. These are fine:install()
,enable()
,disable()
,uninstall()
, andupgrade_XXX()
. -
CRM_*_Upgrader
also inherits some utility and adapter methods. Overriding these can be problematic.- Some overrides are strictly unsupported. Overriding these functions will lead to exceptions:
appendTask()
,onUpgrade()
,getRevisions()
,getCurrentRevision()
,setCurrentRevision()
,enqueuePendingRevisions()
,hasPendingRevisions()
- Some overrides are merely suspicious. They may or may not behave as intended:
notify()
,onInstall()
,onUninstall()
- Some overrides are strictly unsupported. Overriding these functions will lead to exceptions:
If you have an upgrader that cannot be made to work with these restrictions, then you don't have to use
AutomaticUpgrader
. You may alternatively patch your own upgrader to explicitly callE::schema()->install()
andE::schema()->uninstall()
. -
-
EFv2 uses light-weight DAO class-files. The files appear almost empty:
class CRM_MyExtension_DAO_MyEntity extends CRM_MyExtension_DAO_Base { }
All major functions are provided by the base-class. Information is loaded directly from
schema/MyEntity.entityType.php
.The base-class
CRM_MyExtension_DAO_Base
is generally the same asCRM_Core_DAO_Base
(from CiviCRM 5.74+). The exact details depend on your runtime environment. Either:- (Deployed on CiviCRM >=5.74):
CRM_MyExtension_DAO_Base
is literally an alias forCRM_Core_DAO_Base
. - (Deployed on CiviCRM <=5.73):
CRM_MyExtension_DAO_Base
is a polyfill based onCiviMix\Schema\MyExtension\DAO
(a.k.a.mixin/lib/civimix-schema@XXX.phar/src/DAO.php
).
- (Deployed on CiviCRM >=5.74):
-
EFv1 uses heavy-weight DAO class-files. When you run
generate:entity-boilerplate
, it copies information fromxml/schema/*.xml
to the DAO file. The DAO file resembles this skeleton:class CRM_MyExtension_DAO_MyEntity extends CRM_Core_DAO { public static function &fields() { return ...[ field1... data type... html formatting... comments..., field2... data type... html formatting... comments..., ... ]...; } public static function &fieldKeys() { return ...; } public static function &indices() { return ...; } // Ad nauseam... }
-
With EFv2's lightweight DAO, there are more ways to seamlessly evolve the contract (updating
CRM_Core_DAO_Base
and/orCiviMix\Schema\MyExtension\DAO
) without needing to re-generate specific DAO files. -
With EFv2's lightweight DAO, we lose IDE support for auto-completing DAO fields.
- It's expected that this will be less problematic as DAO fields have become a smaller part of overall developer-experience. (APIv3, APIv4, SearchKit, and Afform are more common ways to interact with fields -- and they're not affected.)
- However, if you want IDE auto-complete for fields, then you can add docblocks. This will have no effect on functionality or upgradeability.
/** * @property int $id * @property int $contact_id * @property string $title */ class CRM_MyExtension_DAO_MyEntity extends CRM_MyExtension_DAO_Base {
- EFv2 uses
mixin/lib/civimix-schema@5.XXX.phar
to load helpers likeE::schema()
,CiviMix\Schema\*\AutomaticUpgrader
, andCiviMix\Schema\*\DAO
. - EFv2 uses
mixin/entity-type-php@2.XXX.php
to register entities. - EFv1 uses
mixin/entity-type-php@1.XXX.php
to register entities.