APIv4 - Refactor Entity.get and deprecate the includeCustom param
authorColeman Watts <coleman@civicrm.org>
Wed, 2 Jun 2021 00:23:16 +0000 (20:23 -0400)
committerColeman Watts <coleman@civicrm.org>
Wed, 2 Jun 2021 12:08:27 +0000 (08:08 -0400)
This makes Entity.get more efficient - it no longer does a file scan when getting one or more entities by name.
Also deprecates the includeCustom param which was redundant with the where clause.
This is the first APIv4 param to be deprecated - added handling to emit a warning when using a deprecated param
and hide deprecated params in the APIv4 explorer.

Civi/Api4/Action/Entity/Get.php
Civi/Api4/Entity.php
Civi/Api4/Event/Subscriber/ValidateFieldsSubscriber.php
ang/api4Explorer/Explorer.js

index 765a3703a9f4507e969ff0ffaac1b80b37978f08..fbfe399e4d3ccb4f04f30346fb12e49c279876da 100644 (file)
@@ -29,54 +29,41 @@ use Civi\Api4\Utils\CoreUtil;
  *
  * Scans for api entities in core, enabled components & enabled extensions.
  *
- * Also includes pseudo-entities from multi-record custom groups by default.
- *
- * @method $this setIncludeCustom(bool $value)
- * @method bool getIncludeCustom()
+ * Also includes pseudo-entities from multi-record custom groups.
  */
 class Get extends \Civi\Api4\Generic\BasicGetAction {
 
   /**
-   * Include custom-field-based pseudo-entities?
-   *
    * @var bool
+   * @deprecated
    */
-  protected $includeCustom = TRUE;
+  protected $includeCustom;
 
   /**
    * Scan all api directories to discover entities
    */
   protected function getRecords() {
     $entities = [];
-    $toGet = $this->_itemsToGet('name');
-    $locations = array_merge([\Civi::paths()->getPath('[civicrm.root]/Civi.php')],
-      array_column(\CRM_Extension_System::singleton()->getMapper()->getActiveModuleFiles(), 'filePath')
-    );
-    $enabledComponents = array_keys(\CRM_Core_Component::getEnabledComponents());
-    foreach ($locations as $location) {
-      $dir = \CRM_Utils_File::addTrailingSlash(dirname($location)) . 'Civi/Api4';
-      if (is_dir($dir)) {
-        foreach (glob("$dir/*.php") as $file) {
-          $matches = [];
-          preg_match('/(\w*)\.php$/', $file, $matches);
-          $className = '\Civi\Api4\\' . $matches[1];
-          if (is_a($className, '\Civi\Api4\Generic\AbstractEntity', TRUE)) {
-            $info = $className::getInfo();
-            $entityName = $info['name'];
-            $daoName = $info['dao'] ?? NULL;
-            // Only include DAO entities from enabled components
-            if ((!$toGet || in_array($entityName, $toGet)) &&
-              (!$daoName || !defined("{$daoName}::COMPONENT") || in_array($daoName::COMPONENT, $enabledComponents))
-            ) {
-              $entities[$info['name']] = $info;
-            }
+    $namesRequested = $this->_itemsToGet('name');
+
+    if ($namesRequested) {
+      foreach ($namesRequested as $entityName) {
+        if (strpos($entityName, 'Custom_') !== 0) {
+          $className = CoreUtil::getApiClass($entityName);
+          if ($className) {
+            $this->loadEntity($className, $entities);
           }
         }
       }
     }
+    else {
+      foreach ($this->getAllApiClasses() as $className) {
+        $this->loadEntity($className, $entities);
+      }
+    }
 
     // Fetch custom entities unless we've already fetched everything requested
-    if ($this->includeCustom && (!$toGet || array_diff($toGet, array_keys($entities)))) {
+    if (!$namesRequested || array_diff($namesRequested, array_keys($entities))) {
       $this->addCustomEntities($entities);
     }
 
@@ -84,6 +71,41 @@ class Get extends \Civi\Api4\Generic\BasicGetAction {
     return $entities;
   }
 
+  /**
+   * @param \Civi\Api4\Generic\AbstractEntity $className
+   * @param array $entities
+   */
+  private function loadEntity($className, array &$entities) {
+    $info = $className::getInfo();
+    $daoName = $info['dao'] ?? NULL;
+    // Only include DAO entities from enabled components
+    if (!$daoName || !defined("{$daoName}::COMPONENT") || array_key_exists($daoName::COMPONENT, \CRM_Core_Component::getEnabledComponents())) {
+      $entities[$info['name']] = $info;
+    }
+  }
+
+  /**
+   * @return \Civi\Api4\Generic\AbstractEntity[]
+   */
+  private function getAllApiClasses() {
+    $classNames = [];
+    $locations = array_merge([\Civi::paths()->getPath('[civicrm.root]/Civi.php')],
+      array_column(\CRM_Extension_System::singleton()->getMapper()->getActiveModuleFiles(), 'filePath')
+    );
+    foreach ($locations as $location) {
+      $dir = \CRM_Utils_File::addTrailingSlash(dirname($location)) . 'Civi/Api4';
+      if (is_dir($dir)) {
+        foreach (glob("$dir/*.php") as $file) {
+          $className = 'Civi\Api4\\' . basename($file, '.php');
+          if (is_a($className, 'Civi\Api4\Generic\AbstractEntity', TRUE)) {
+            $classNames[] = $className;
+          }
+        }
+      }
+    }
+    return $classNames;
+  }
+
   /**
    * Add custom-field pseudo-entities
    *
index aa67b99a8ae9311b83ba2812234feeaf6b460c34..00a99f6b331aeb49cba3e44bcd1966ed739bf95c 100644 (file)
@@ -64,6 +64,7 @@ class Entity extends Generic\AbstractEntity {
           'options' => [
             'AbstractEntity' => 'AbstractEntity',
             'DAOEntity' => 'DAOEntity',
+            'CustomValue' => 'CustomValue',
             'BasicEntity' => 'BasicEntity',
             'EntityBridge' => 'EntityBridge',
             'OptionList' => 'OptionList',
index e1aaa652161735bc49e62509f1585871753f4a2b..65b7cc1bdfe17ffec509db315c6164141e61969a 100644 (file)
@@ -37,6 +37,9 @@ class ValidateFieldsSubscriber extends Generic\AbstractPrepareSubscriber {
         if (!empty($info['type']) && !self::checkType($value, $info['type'])) {
           throw new \API_Exception('Parameter "' . $param . '" is not of the correct type. Expecting ' . implode(' or ', $info['type']) . '.');
         }
+        if (!empty($info['deprecated']) && isset($value)) {
+          \CRM_Core_Error::deprecatedWarning('APIv4 ' . $apiRequest->getEntityName() . ".$param parameter is deprecated.");
+        }
       }
     }
   }
index 72cc5c6fa5b1a08281dc4ba8561de18600ff98c8..5cc419a6461dd3709863328c71aee24befccdbd3 100644 (file)
         specialParams.push('limit', 'offset');
       }
       return _.transform($scope.availableParams, function(genericParams, param, name) {
-        if (!_.contains(specialParams, name) &&
+        if (!_.contains(specialParams, name) && !param.deprecated &&
           !(typeof paramType !== 'undefined' && !_.contains(paramType, param.type[0])) &&
           !(typeof defaultNull !== 'undefined' && ((param.default === null) !== defaultNull))
         ) {