APIv4 - Deprecate nonstandard syntax for implicit joins
authorColeman Watts <coleman@civicrm.org>
Fri, 23 Apr 2021 14:55:45 +0000 (10:55 -0400)
committerColeman Watts <coleman@civicrm.org>
Fri, 23 Apr 2021 14:55:45 +0000 (10:55 -0400)
The APIv4 prototype used a string-manipulation hack to make implicit joins appear more friendly,
e.g. it would transform `contact_id.*` to `contact.*`.
However this made the generation of such joins less predictable,
as not all FK fields end in `_id` (notably, custom ContactRef fields do not).

This preserves the nonstandard syntax for backward-compat while favoring the new syntx
in the API Explorer.

Civi/Api4/Action/Entity/GetLinks.php
Civi/Api4/Service/Schema/Joinable/Joinable.php
Civi/Api4/Service/Schema/SchemaMapBuilder.php
tests/phpunit/api/v4/Entity/ContactJoinTest.php

index 2d224da3355936ba034533575b9115e88564201a..59b152eab7f02897470b00a88578a6ee45a2c35a 100644 (file)
@@ -40,7 +40,9 @@ class GetLinks extends \Civi\Api4\Generic\BasicGetAction {
           'links' => [],
         ];
         foreach ($table->getTableLinks() as $link) {
-          $item['links'][] = $link->toArray();
+          if (!$link->isDeprecated()) {
+            $item['links'][] = $link->toArray();
+          }
         }
         $result[] = $item;
       }
index 8e6d4e71168017dd010d240798f2547a69ae9472..b45b8c685604d3cd62b0707869601bc3cec935c2 100644 (file)
@@ -78,6 +78,11 @@ class Joinable {
    */
   protected $entity;
 
+  /**
+   * @var bool
+   */
+  protected $deprecated = FALSE;
+
   /**
    * @param $targetTable
    * @param $targetColumn
@@ -252,6 +257,23 @@ class Joinable {
     return $this;
   }
 
+  /**
+   * @return bool
+   */
+  public function isDeprecated() {
+    return $this->deprecated;
+  }
+
+  /**
+   * @param bool $deprecated
+   *
+   * @return $this
+   */
+  public function setDeprecated(bool $deprecated = TRUE) {
+    $this->deprecated = $deprecated;
+    return $this;
+  }
+
   /**
    * @return array
    */
index 77666cb4a78d1137f1fdef6d6b70990a2ebaf3c2..752e729caae116ed7b741da9700b12f10551d028 100644 (file)
@@ -91,9 +91,15 @@ class SchemaMapBuilder {
     if ($fkClass) {
       $tableName = AllCoreTables::getTableForClass($fkClass);
       $fkKey = $data['FKKeyColumn'] ?? 'id';
-      // Fixme: Clumsy string manipulation to transform e.g. "contact_id" to "contact" - we never should have done this
-      $alias = str_replace('_id', '', $field);
-      $joinable = new Joinable($tableName, $fkKey, $alias);
+      // Backward-compatibility for older api calls using e.g. "contact" instead of "contact_id"
+      if (strpos($field, '_id')) {
+        $alias = str_replace('_id', '', $field);
+        $joinable = new Joinable($tableName, $fkKey, $alias);
+        $joinable->setJoinType($joinable::JOIN_TYPE_MANY_TO_ONE);
+        $joinable->setDeprecated();
+        $table->addTableLink($field, $joinable);
+      }
+      $joinable = new Joinable($tableName, $fkKey, $field);
       $joinable->setJoinType($joinable::JOIN_TYPE_MANY_TO_ONE);
       $table->addTableLink($field, $joinable);
     }
index a97e6266cc2e29c3d00bcd23e8c1be165f69494f..0c92abac5441cb18c4efbc66f05ab53d7902fdc7 100644 (file)
@@ -46,14 +46,14 @@ class ContactJoinTest extends UnitTestCase {
     return parent::setUpHeadless();
   }
 
-  public function testContactJoin() {
-
+  public function testContactJoinDeprecated() {
     $contact = $this->getReference('test_contact_1');
     $entitiesToTest = ['Address', 'OpenID', 'IM', 'Website', 'Email', 'Phone'];
 
     foreach ($entitiesToTest as $entity) {
       $results = civicrm_api4($entity, 'get', [
         'where' => [['contact_id', '=', $contact['id']]],
+        // Deprecated syntax (new syntax is `contact_id.*` not `contact.*`)
         'select' => ['contact.*_name', 'contact.id'],
       ]);
       foreach ($results as $result) {
@@ -63,6 +63,22 @@ class ContactJoinTest extends UnitTestCase {
     }
   }
 
+  public function testContactJoin() {
+    $contact = $this->getReference('test_contact_1');
+    $entitiesToTest = ['Address', 'OpenID', 'IM', 'Website', 'Email', 'Phone'];
+
+    foreach ($entitiesToTest as $entity) {
+      $results = civicrm_api4($entity, 'get', [
+        'where' => [['contact_id', '=', $contact['id']]],
+        'select' => ['contact_id.*_name', 'contact_id.id'],
+      ]);
+      foreach ($results as $result) {
+        $this->assertEquals($contact['id'], $result['contact_id.id']);
+        $this->assertEquals($contact['display_name'], $result['contact_id.display_name']);
+      }
+    }
+  }
+
   public function testJoinToPCMWillReturnArray() {
     $contact = Contact::create()->setValues([
       'preferred_communication_method' => [1, 2, 3],