APIv4 - Add entity metadata about class args
authorColeman Watts <coleman@civicrm.org>
Fri, 25 Feb 2022 15:42:29 +0000 (10:42 -0500)
committerColeman Watts <coleman@civicrm.org>
Sun, 27 Feb 2022 01:16:12 +0000 (20:16 -0500)
The CustomValue API is a virtual API, where multiple entities all get routed to the
same class if they share the prefix "Custom_", and pass a class arg to the
php factory functions e.g. `CustomValue::get('MyCustomGroup')`.
Instead of hard-coding this idea into the  now it's part of the entity
metadata so that other APIs, notaby ECK, can use a similar pattern.

Civi/Api4/Entity.php
Civi/Api4/Provider/CustomEntityProvider.php
ang/api4Explorer/Explorer.js
tests/phpunit/api/v4/Action/CustomValueTest.php

index bc6b18bc6da09d21d15ec72b99e4718b7c37ff2e..915e739d50466c77431c4c5236d26c5826a229fd 100644 (file)
@@ -126,6 +126,11 @@ class Entity extends Generic\AbstractEntity {
           'data_type' => 'String',
           'description' => 'PHP class name',
         ],
+        [
+          'name' => 'class_args',
+          'data_type' => 'Array',
+          'description' => 'Arguments needed by php action factory functions (used when multiple entities share a class, e.g. CustomValue).',
+        ],
         [
           'name' => 'bridge',
           'data_type' => 'Array',
index 9acb55dbbb55c41980fd9399655f888d5873771c..11b324cd7fd64746aa47ff1d3dac7cebc4651b38 100644 (file)
@@ -29,27 +29,28 @@ class CustomEntityProvider {
       ->toSQL();
     $group = \CRM_Core_DAO::executeQuery($select);
     while ($group->fetch()) {
-      $fieldName = 'Custom_' . $group->name;
+      $entityName = 'Custom_' . $group->name;
       $baseEntity = CoreUtil::getApiClass(CustomGroupJoinable::getEntityFromExtends($group->extends));
-      $e->entities[$fieldName] = [
-        'name' => $fieldName,
+      $e->entities[$entityName] = [
+        'name' => $entityName,
         'title' => $group->title,
         'title_plural' => $group->title,
         'table_name' => $group->table_name,
+        'class_args' => [$group->name],
         'description' => ts('Custom group for %1', [1 => $baseEntity::getInfo()['title_plural']]),
         'paths' => [
           'view' => "civicrm/contact/view/cd?reset=1&gid={$group->id}&recId=[id]&multiRecordDisplay=single",
         ],
       ] + $baseInfo;
       if (!empty($group->icon)) {
-        $e->entities[$fieldName]['icon'] = $group->icon;
+        $e->entities[$entityName]['icon'] = $group->icon;
       }
       if (!empty($group->help_pre)) {
-        $e->entities[$fieldName]['comment'] = self::plainTextify($group->help_pre);
+        $e->entities[$entityName]['comment'] = self::plainTextify($group->help_pre);
       }
       if (!empty($group->help_post)) {
-        $pre = empty($e->entities[$fieldName]['comment']) ? '' : $e->entities[$fieldName]['comment'] . "\n\n";
-        $e->entities[$fieldName]['comment'] = $pre . self::plainTextify($group->help_post);
+        $pre = empty($e->entities[$entityName]['comment']) ? '' : $e->entities[$entityName]['comment'] . "\n\n";
+        $e->entities[$entityName]['comment'] = $pre . self::plainTextify($group->help_post);
       }
     }
   }
index 151aa1c8d400bf4d602fd255e4974495b1ce38f9..cedc623e3867ad7bb8b220ea509c0e53f86ec873 100644 (file)
       var code = {},
         entity = $scope.entity,
         action = $scope.action,
+        args = getEntity(entity).class_args || [],
         params = getParams(),
         index = isInt($scope.index) ? +$scope.index : parseYaml($scope.index),
         result = 'result';
       if ($scope.entity && $scope.action) {
         delete params.debug;
         if (action.slice(0, 3) === 'get') {
-          result = entity.substr(0, 7) === 'Custom_' ? _.camelCase(entity.substr(7)) : entity;
+          result = args[0] ? _.camelCase(args[0]) : entity;
           result = lcfirst(action.replace(/s$/, '').slice(3) || result);
         }
         var results = lcfirst(_.isNumber(index) ? result : pluralize(result)),
         arrayParams = ['groupBy', 'records'],
         newLine = "\n" + _.repeat(' ', indent),
         code = '\\' + info.class + '::' + action + '(',
-        perm = params.checkPermissions === false ? 'FALSE' : '';
-      if (entity.substr(0, 7) !== 'Custom_') {
-        code += perm + ')';
-      } else {
-        code += "'" + entity.substr(7) + "'" + (perm ? ', ' : '') + perm + ")";
+        args = _.cloneDeep(info.class_args || []);
+      if (params.checkPermissions === false) {
+        args.push(false);
       }
+      code += _.map(args, phpFormat).join(', ') + ')';
       _.each(params, function(param, key) {
         var val = '';
         if (typeof objectParams[key] !== 'undefined' && key !== 'chain') {
index 68e510d06d15eed6790fb16d1155a3082bfea0db..337cc3c5903a355cc45b1a777bb7ea6aa022e4b3 100644 (file)
@@ -23,6 +23,7 @@ use Civi\Api4\CustomField;
 use Civi\Api4\CustomGroup;
 use Civi\Api4\CustomValue;
 use Civi\Api4\Contact;
+use Civi\Api4\Entity;
 
 /**
  * @group headless
@@ -79,6 +80,17 @@ class CustomValueTest extends BaseCustomValueTest {
       ->execute()
       ->first()['id'];
 
+    // Ensure virtual api entity has been created
+    $entity = Entity::get(FALSE)
+      ->addWhere('name', '=', "Custom_$group")
+      ->execute()->single();
+    $this->assertEquals(['CustomValue'], $entity['type']);
+    $this->assertEquals(['id'], $entity['primary_key']);
+    $this->assertEquals($customGroup['table_name'], $entity['table_name']);
+    $this->assertEquals('Civi\Api4\CustomValue', $entity['class']);
+    $this->assertEquals([$group], $entity['class_args']);
+    $this->assertEquals('secondary', $entity['searchable']);
+
     // Retrieve and check the fields of CustomValue = Custom_$group
     $fields = CustomValue::getFields($group)->setLoadOptions(TRUE)->setCheckPermissions(FALSE)->execute();
     $expectedResult = [