APIv4 - Use event instead of hardcoded list to resolve entityName to className
authorColeman Watts <coleman@civicrm.org>
Fri, 8 Oct 2021 19:23:59 +0000 (15:23 -0400)
committerColeman Watts <coleman@civicrm.org>
Fri, 8 Oct 2021 19:23:59 +0000 (15:23 -0400)
Typically an APIv4 entity name and class name are the same,
e.g. the "Contact" entity has the class `Civi\Api4\Contact`.
However, there are a few oddballs (CiviCase, CustomValue) and other extensions
may introduce their own oddball entities; this event allows them to do so.

Civi/API/Request.php
Civi/Api4/Event/CreateApi4RequestEvent.php [new file with mode: 0644]
Civi/Api4/Event/Subscriber/CreateApi4RequestSubscriber.php [new file with mode: 0644]
Civi/Api4/Utils/CoreUtil.php

index 5ad9b9d21996acc4424cd11b620bdae0609f50b9..4c9a6d5fc0a9418eb6a97d8ec3f1199017c36256 100644 (file)
@@ -10,7 +10,7 @@
  */
 namespace Civi\API;
 
-use Civi\Api4\Utils\CoreUtil;
+use Civi\Api4\Event\CreateApi4RequestEvent;
 
 /**
  * Class Request
@@ -45,17 +45,13 @@ class Request {
         ];
 
       case 4:
-        // For custom pseudo-entities
-        if (strpos($entity, 'Custom_') === 0) {
-          $apiRequest = \Civi\Api4\CustomValue::$action(substr($entity, 7));
-        }
-        else {
-          $callable = [CoreUtil::getApiClass($entity), $action];
-          if (!is_callable($callable)) {
-            throw new \Civi\API\Exception\NotImplementedException("API ($entity, $action) does not exist (join the API team and implement it!)");
-          }
-          $apiRequest = call_user_func($callable);
+        $e = new CreateApi4RequestEvent($entity);
+        \Civi::dispatcher()->dispatch('civi.api4.createRequest', $e);
+        $callable = [$e->className, $action];
+        if (!$e->className || !is_callable($callable)) {
+          throw new \Civi\API\Exception\NotImplementedException("API ($entity, $action) does not exist (join the API team and implement it!)");
         }
+        $apiRequest = call_user_func_array($callable, $e->args);
         foreach ($params as $name => $param) {
           $setter = 'set' . ucfirst($name);
           $apiRequest->$setter($param);
diff --git a/Civi/Api4/Event/CreateApi4RequestEvent.php b/Civi/Api4/Event/CreateApi4RequestEvent.php
new file mode 100644 (file)
index 0000000..7a77989
--- /dev/null
@@ -0,0 +1,56 @@
+<?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       |
+ +--------------------------------------------------------------------+
+ */
+namespace Civi\Api4\Event;
+
+use Civi\Core\Event\GenericHookEvent;
+
+/**
+ * civi.api4.createRequest event.
+ *
+ * This event fires whenever resolving the name of an api entity to an api class.
+ *
+ * e.g. the entity name "Contact" resolves to the class `Civi\Api4\Contact`
+ * and the entity "Case" resolves to `Civi\Api4\CiviCase`
+ */
+class CreateApi4RequestEvent extends GenericHookEvent {
+
+  /**
+   * Name of the entity to matched to an api class
+   *
+   * @var string
+   */
+  public $entityName;
+
+  /**
+   * Resolved fully-namespaced class name.
+   *
+   * @var string
+   */
+  public $className;
+
+  /**
+   * Additional arguments which should be passed to the action factory function.
+   *
+   * For example, `Civi\Api4\CustomValue` factory functions require the name of the custom group.
+   *
+   * @var array
+   */
+  public $args = [];
+
+  /**
+   * Event constructor
+   */
+  public function __construct($entityName) {
+    $this->entityName = $entityName;
+  }
+
+}
diff --git a/Civi/Api4/Event/Subscriber/CreateApi4RequestSubscriber.php b/Civi/Api4/Event/Subscriber/CreateApi4RequestSubscriber.php
new file mode 100644 (file)
index 0000000..45cf5dd
--- /dev/null
@@ -0,0 +1,52 @@
+<?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       |
+ +--------------------------------------------------------------------+
+ */
+
+namespace Civi\Api4\Event\Subscriber;
+
+use Civi\API\Events;
+use Civi\Api4\Utils\CoreUtil;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Resolve class for core and custom entities
+ */
+class CreateApi4RequestSubscriber implements EventSubscriberInterface {
+
+  /**
+   * @return array
+   */
+  public static function getSubscribedEvents() {
+    return [
+      'civi.api4.createRequest' => [
+        ['onApiRequestCreate', Events::W_LATE],
+      ],
+    ];
+  }
+
+  /**
+   * @param \Civi\Api4\Event\CreateApi4RequestEvent $event
+   */
+  public function onApiRequestCreate(\Civi\Api4\Event\CreateApi4RequestEvent $event) {
+    if (strpos($event->entityName, 'Custom_') === 0) {
+      $groupName = substr($event->entityName, 7);
+      if (CoreUtil::isCustomEntity($groupName)) {
+        $event->className = 'Civi\Api4\CustomValue';
+        $event->args = [$groupName];
+      }
+    }
+    // Because "Case" is a reserved php keyword
+    $className = 'Civi\Api4\\' . ($event->entityName === 'Case' ? 'CiviCase' : $event->entityName);
+    if (class_exists($className)) {
+      $event->className = $className;
+    }
+  }
+
+}
index 9643585769ae016a545a0e12da4a6cef850b8243..1dbf6316039a92022f9ea992f0f7fa69716f592a 100644 (file)
@@ -13,6 +13,7 @@
 namespace Civi\Api4\Utils;
 
 use Civi\API\Request;
+use Civi\Api4\Event\CreateApi4RequestEvent;
 use CRM_Core_DAO_AllCoreTables as AllCoreTables;
 
 class CoreUtil {
@@ -43,13 +44,9 @@ class CoreUtil {
    * @return string|\Civi\Api4\Generic\AbstractEntity
    */
   public static function getApiClass($entityName) {
-    if (strpos($entityName, 'Custom_') === 0) {
-      $groupName = substr($entityName, 7);
-      return self::isCustomEntity($groupName) ? 'Civi\Api4\CustomValue' : NULL;
-    }
-    // Because "Case" is a reserved php keyword
-    $className = 'Civi\Api4\\' . ($entityName === 'Case' ? 'CiviCase' : $entityName);
-    return class_exists($className) ? $className : NULL;
+    $e = new CreateApi4RequestEvent($entityName);
+    \Civi::dispatcher()->dispatch('civi.api4.createRequest', $e);
+    return $e->className;
   }
 
   /**
@@ -164,7 +161,7 @@ class CoreUtil {
    * @return bool
    * @throws \CRM_Core_Exception
    */
-  private static function isCustomEntity($customGroupName) {
+  public static function isCustomEntity($customGroupName) {
     return $customGroupName && \CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupName, 'is_multiple', 'name');
   }