Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(dev/core#4999) Convert xml schema to new entityType.php format #29472

Merged
merged 22 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5a9cf6d
Add EntityRepository class
colemanw Feb 22, 2024
b1bab48
Add EntityProvider, EntityMetadata & EntityStorage interfaces
colemanw Feb 22, 2024
3167167
dev/core#4999 Add CRM_Core_DAO_Base class
colemanw Mar 3, 2024
24b1fea
Add Civi\Schema\SqlGenerator
colemanw Mar 19, 2024
64fc492
Import converted schema files (xml/schema/**.xml ==> schema/**.entity…
totten Mar 25, 2024
63214d0
SqlGenerator - Make it clear how to initialize in pre-boot/pre-instal…
totten Mar 25, 2024
f45171b
Use SqlGenerator instead of schema.tpl, drop.tpl
totten Mar 25, 2024
5e4fd10
SqlGenerator - Add FKs after tables instead of sorting
colemanw Mar 25, 2024
5c7cb8d
Add pathload-0.php
totten Feb 16, 2024
418f436
Add script to generate [email protected] for backports
totten Feb 16, 2024
75a4664
ClassLoader - Include mixin/lib/
totten Feb 16, 2024
d4e8256
civimix-schema - Add skeleton library with `SchemaHelperInterface`
totten Mar 27, 2024
69dbd0d
Civi::schemaHelper() - Facade for getting the core-core schema helper
totten Mar 27, 2024
d540161
civimix-schema - Migrate SqlGenerator. Use it to implement SchemaHelper.
totten Mar 27, 2024
0284ff0
SqlGenerator - For new installations, assume utf8mb4_unicode_ci
totten Mar 26, 2024
2aaa167
SqlGenerator - Don't fail if there are no indices.
totten Mar 26, 2024
0a5bccf
SqlGenerator - Make createFromFolder() a closer match to entity-types…
totten Mar 26, 2024
4deceb1
civimix-schema - Add \CiviMix\Schema\*\AutomaticUpgrader
totten Mar 27, 2024
933c73e
SqlGenerator - Make ON DELETE optional
colemanw Apr 3, 2024
26c8d6b
Expect data_type as string, convert to constant in dao
colemanw Apr 16, 2024
ed18b34
Don't force boolean fields to be NOT NULL
colemanw Apr 16, 2024
12788a9
Fix generated sql for NULL timestamps
colemanw Apr 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CRM/Core/BAO/SchemaHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
*/
class CRM_Core_BAO_SchemaHandler {

const DEFAULT_COLLATION = 'utf8mb4_unicode_ci';

/**
* Create a CiviCRM-table
*
Expand Down Expand Up @@ -899,7 +901,7 @@ public static function getInUseCollation(): string {
if (!isset(\Civi::$statics[__CLASS__][__FUNCTION__])) {
$dao = CRM_Core_DAO::executeQuery('SHOW TABLE STATUS LIKE \'civicrm_contact\'');
$dao->fetch();
\Civi::$statics[__CLASS__][__FUNCTION__] = $dao->Collation;
\Civi::$statics[__CLASS__][__FUNCTION__] = $dao->Collation ?? self::DEFAULT_COLLATION;
}
return \Civi::$statics[__CLASS__][__FUNCTION__];
}
Expand Down
263 changes: 263 additions & 0 deletions CRM/Core/CRM/Core/DAO/Base.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
<?php
/*
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC. All rights reserved. |
| |
| This work is published under the GNU AGPLv3 license with some |
| permitted exceptions and without any warranty. For full license |
| and copyright information, see https://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/

/**
* Base for concrete DAO/BAO classes which are defined with a schema/entityType.php file.
*/
abstract class CRM_Core_DAO_Base extends CRM_Core_DAO {

/**
* @inheritDoc
*/
public function keys(): array {
$keys = [];
foreach (static::getEntityDefinition()['getFields']() as $name => $field) {
if (!empty($field['primary_key'])) {
$keys[] = $name;
}
}
return $keys;
}

public static function getEntityTitle($plural = FALSE) {
$info = static::getEntityInfo();
return ($plural && isset($info['title_plural'])) ? $info['title_plural'] : $info['title'];
}

/**
* @inheritDoc
*/
public static function getEntityPaths(): array {
$definition = static::getEntityDefinition();
if (isset($definition['getPaths'])) {
return $definition['getPaths']();
}
return [];
}

/**
* @inheritDoc
*/
public static function getEntityDescription(): ?string {
return static::getEntityInfo()['description'] ?? NULL;
}

/**
* @inheritDoc
*/
public static function getTableName() {
return static::getEntityDefinition()['table'];
}

/**
* @inheritDoc
*/
public function getLog(): bool {
return static::getEntityInfo()['log'] ?? FALSE;
}

/**
* @inheritDoc
*/
public static function getEntityIcon(string $entityName, int $entityId = NULL): ?string {
return static::getEntityInfo()['icon'] ?? NULL;
}

/**
* @inheritDoc
*/
protected static function getTableAddVersion(): string {
return static::getEntityInfo()['add'] ?? '1.0';
}

/**
* @inheritDoc
*/
public static function getExtensionName(): ?string {
return static::getEntityDefinition()['module'];
}

/**
* @inheritDoc
*/
public static function &fields() {
$fields = [];
foreach (static::getSchemaFields() as $field) {
$key = $field['uniqueName'] ?? $field['name'];
unset($field['uniqueName']);
$fields[$key] = $field;
}
return $fields;
}

private static function getSchemaFields(): array {
return (Civi::$statics[static::class]['fields'] ??= static::loadSchemaFields());
}

private static function loadSchemaFields(): array {
$fields = [];
$entityDef = static::getEntityDefinition();
$baoName = CRM_Core_DAO_AllCoreTables::getBAOClassName(static::class);

foreach ($entityDef['getFields']() as $fieldName => $fieldSpec) {
$field = [
'name' => $fieldName,
'type' => !empty($fieldSpec['data_type']) ? \CRM_Utils_Type::getValidTypes()[$fieldSpec['data_type']] : constant(\CRM_Utils_Schema::getCrmTypeFromSqlType($fieldSpec['sql_type'])),
'title' => $fieldSpec['title'],
'description' => $fieldSpec['description'] ?? NULL,
];
if (!empty($fieldSpec['required'])) {
$field['required'] = TRUE;
}
if (str_starts_with($fieldSpec['sql_type'], 'decimal(')) {
$precision = self::getFieldLength($fieldSpec['sql_type']);
$field['precision'] = explode(',', $precision);
}
foreach (['maxlength', 'size', 'rows', 'cols'] as $attr) {
if (isset($fieldSpec['input_attrs'][$attr])) {
$field[$attr] = $fieldSpec['input_attrs'][$attr];
unset($fieldSpec['input_attrs'][$attr]);
}
}
if (!isset($field['size']) && str_contains($fieldSpec['sql_type'], 'char')) {
$length = self::getFieldLength($fieldSpec['sql_type']);
$field['size'] = constant(CRM_Utils_Schema::getDefaultSize($length));
}
$usage = $fieldSpec['usage'] ?? [];
$field['usage'] = [
'import' => in_array('import', $usage),
'export' => in_array('export', $usage),
'duplicate_matching' => in_array('duplicate_matching', $usage),
'token' => in_array('token', $usage),
];
if ($field['usage']['import']) {
$field['import'] = TRUE;
}
$field['where'] = $entityDef['table'] . '.' . $field['name'];
if ($field['usage']['export'] || (!$field['usage']['export'] && $field['usage']['import'])) {
$field['export'] = $field['usage']['export'];
}
if (!empty($fieldSpec['contact_type'])) {
$field['contactType'] = $fieldSpec['contact_type'];
}
if (!empty($fieldSpec['permission'])) {
$field['permission'] = $fieldSpec['permission'];
}
if (array_key_exists('default', $fieldSpec)) {
$field['default'] = isset($fieldSpec['default']) ? (string) $fieldSpec['default'] : NULL;
if (is_bool($fieldSpec['default'])) {
$field['default'] = $fieldSpec['default'] ? '1' : '0';
}
}
$field['table_name'] = $entityDef['table'];
$field['entity'] = $entityDef['name'];
$field['bao'] = $baoName;
$field['localizable'] = intval($fieldSpec['localizable'] ?? 0);
if (!empty($fieldSpec['localize_context'])) {
$field['localize_context'] = (string) $fieldSpec['localize_context'];
}
if (!empty($fieldSpec['entity_reference'])) {
if (!empty($fieldSpec['entity_reference']['entity'])) {
$field['FKClassName'] = CRM_Core_DAO_AllCoreTables::getDAONameForEntity($fieldSpec['entity_reference']['entity']);
}
if (!empty($fieldSpec['entity_reference']['dynamic_entity'])) {
$field['DFKEntityColumn'] = $fieldSpec['entity_reference']['dynamic_entity'];
}
$field['FKColumnName'] = $fieldSpec['entity_reference']['key'] ?? 'id';
}
if (!empty($fieldSpec['component'])) {
$field['component'] = $fieldSpec['component'];
}
if (!empty($fieldSpec['serialize'])) {
$field['serialize'] = $fieldSpec['serialize'];
}
if (!empty($fieldSpec['unique_name'])) {
$field['uniqueName'] = $fieldSpec['unique_name'];
}
if (!empty($fieldSpec['unique_title'])) {
$field['uniqueTitle'] = $fieldSpec['unique_title'];
}
if (!empty($fieldSpec['deprecated'])) {
$field['deprecated'] = TRUE;
}
if (!empty($fieldSpec['input_attrs'])) {
$field['html'] = CRM_Utils_Array::rekey($fieldSpec['input_attrs'], fn($str) => CRM_Utils_String::convertStringToCamel($str, FALSE));
}
if (!empty($fieldSpec['input_type'])) {
$field['html']['type'] = $fieldSpec['input_type'];
}
if (!empty($fieldSpec['pseudoconstant'])) {
$field['pseudoconstant'] = CRM_Utils_Array::rekey($fieldSpec['pseudoconstant'], fn($str) => CRM_Utils_String::convertStringToCamel($str, FALSE));
if (!isset($field['pseudoconstant']['optionEditPath']) && !empty($field['pseudoconstant']['optionGroupName'])) {
$field['pseudoconstant']['optionEditPath'] = 'civicrm/admin/options/' . $field['pseudoconstant']['optionGroupName'];
}
}
if (!empty($fieldSpec['primary_key']) || !empty($field['readonly'])) {
$field['readonly'] = TRUE;
}
$field['add'] = $fieldSpec['add'] ?? NULL;
$fields[$fieldName] = $field;
}
CRM_Core_DAO_AllCoreTables::invoke(static::class, 'fields_callback', $fields);
return $fields;
}

private static function getFieldLength($sqlType): ?string {
$open = strpos($sqlType, '(');
if ($open) {
return substr($sqlType, $open + 1, -1);
}
return NULL;
}

/**
* @inheritDoc
*/
public static function indices(bool $localize = TRUE): array {
$definition = static::getEntityDefinition();
$indices = [];
if (isset($definition['getIndices'])) {
$fields = $definition['getFields']();
foreach ($definition['getIndices']() as $name => $info) {
$index = [
'name' => $name,
'field' => [],
'localizable' => FALSE,
];
foreach ($info['fields'] as $fieldName => $length) {
if (!empty($fields[$fieldName]['localizable'])) {
$index['localizable'] = TRUE;
}
if (is_int($length)) {
$fieldName .= "($length)";
}
$index['field'][] = $fieldName;
}
if (!empty($info['unique'])) {
$index['unique'] = TRUE;
}
$index['sig'] = ($definition['table']) . '::' . intval($info['unique'] ?? 0) . '::' . implode('::', $index['field']);
$indices[$name] = $index;
}
}
return ($localize && $indices) ? CRM_Core_DAO_AllCoreTables::multilingualize(static::class, $indices) : $indices;
}

private static function getEntityDefinition(): array {
$entityName = CRM_Core_DAO_AllCoreTables::getEntityNameForClass(static::class);
return \Civi\Schema\EntityRepository::getEntity($entityName);
}

private static function getEntityInfo(): array {
return static::getEntityDefinition()['getInfo']();
}

}
4 changes: 4 additions & 0 deletions CRM/Core/ClassLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ public function register($prepend = FALSE) {
set_include_path($include_paths . PATH_SEPARATOR . get_include_path());
// @todo Why do we need to load this again?
$this->requireComposerAutoload();

$mixinLib = dirname(__DIR__, 2) . '/mixin/lib';
($GLOBALS['_PathLoad'][0] ?? require "$mixinLib/pathload-0.php");
require_once "$mixinLib/pathload.index.php";
}

/**
Expand Down
2 changes: 1 addition & 1 deletion CRM/Core/CodeGen/Main.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public function getTasks() {
$tasks = [];
$tasks[] = new CRM_Core_CodeGen_Config($this);
$tasks[] = new CRM_Core_CodeGen_Reflection($this);
$tasks[] = new CRM_Core_CodeGen_Schema($this);
$tasks[] = new CRM_Core_CodeGen_PhpSchema($this);
foreach (array_keys($this->tables) as $name) {
$tasks[] = new CRM_Core_CodeGen_DAO($this, $name);
}
Expand Down
Loading