GenCode, Cache::cleanKey() - Fix deploop during clean initialization
authorTim Otten <totten@civicrm.org>
Wed, 10 Jul 2019 02:21:58 +0000 (19:21 -0700)
committerTim Otten <totten@civicrm.org>
Wed, 10 Jul 2019 05:42:07 +0000 (22:42 -0700)
Overview
--------

Suppose you have a clean codebase with no DAO files, and you try to run `xml/GenCode.php`.
This currently crashes.

The problem is not symptomatic day-to-day because we commit DAOs, so it's
very rare to run a full/clean initialization.  However, it does get in the
way of stress-testing the correctness of GenCode.

Before
------

Since 76e697a9d750d568d0d12c10dd8173f656e8bb1e, I believe we've had a dependency-loop
in the clean-gencode scenario, which works as follows:

* We don't have any DAOs, so we run `GenCode`.
* Running `GenCode` does a partial bootstrap (i.e. `CRM_Core_Config::singleton($loadFromDB===FALSE)`)
* The partial bootstrap creates some thread-local caches (`Arraycache`)
* Before creating the cache, it normalizes the name by calling `CRM_Core_BAO_Cache::cleanKey()`
* `CRM_Core_BAO_Cache` extends `CRM_Core_DAO_Cache`
* `CRM_Core_BAO_Cache` doesn't exist - that's why we called `GenCode` at the beginning.

After
-----

This migrates the utility function `cleanKey()` to another class which is always available and
does not rely on DAOs.

CRM/ACL/BAO/ACL.php
CRM/Contact/BAO/ContactType.php
CRM/Core/BAO/Cache.php
CRM/Core/BAO/Cache/Psr16.php
CRM/Core/BAO/PrevNextCache.php
CRM/Core/PseudoConstant.php
CRM/Utils/Cache.php
CRM/Utils/Cache/SqlGroup.php
tests/phpunit/CRM/Core/BAO/CacheTest.php

index 0051422252ec47a65ad16b2800e200e2599b0d9c..abf87247bbe8e073e7512bfe08bbc48d0c6be8ff 100644 (file)
@@ -851,7 +851,7 @@ SELECT g.*
       $aclKeys = array_keys($acls);
       $aclKeys = implode(',', $aclKeys);
 
-      $cacheKey = CRM_Core_BAO_Cache::cleanKey("$tableName-$aclKeys");
+      $cacheKey = CRM_Utils_Cache::cleanKey("$tableName-$aclKeys");
       $cache = CRM_Utils_Cache::singleton();
       $ids = $cache->get($cacheKey);
       if (!$ids) {
index b0d25258ef6d60ea2280d4a4781cff46659651c7..8ee3011f2779d071fa1c2f249b39a9bdff1ffd17 100644 (file)
@@ -387,7 +387,7 @@ WHERE  type.name IS NOT NULL
     $argString = $all ? 'CRM_CT_GSE_1' : 'CRM_CT_GSE_0';
     $argString .= $isSeparator ? '_1' : '_0';
     $argString .= $separator;
-    $argString = CRM_Core_BAO_Cache::cleanKey($argString);
+    $argString = CRM_Utils_Cache::cleanKey($argString);
     if (!array_key_exists($argString, $_cache)) {
       $cache = CRM_Utils_Cache::singleton();
       $_cache[$argString] = $cache->get($argString);
index ef2bc60eee6ac8003dcba1ed3ea3a9b3bb70070a..d5b4ade2f2b859efdb0acd6d7ce070f3b7ed0779 100644 (file)
@@ -475,24 +475,11 @@ class CRM_Core_BAO_Cache extends CRM_Core_DAO_Cache {
    * @return string
    *   Ex: '_abcd1234abcd1234' or 'ab_xx/cd_xxef'.
    *   A similar key, but suitable for use with PSR-16-compliant cache providers.
+   * @deprecated
+   * @see CRM_Utils_Cache::cleanKey()
    */
   public static function cleanKey($key) {
-    if (!is_string($key) && !is_int($key)) {
-      throw new \RuntimeException("Malformed cache key");
-    }
-
-    $maxLen = 64;
-    $escape = '-';
-
-    if (strlen($key) >= $maxLen) {
-      return $escape . md5($key);
-    }
-
-    $r = preg_replace_callback(';[^A-Za-z0-9_\.];', function($m) use ($escape) {
-      return $escape . dechex(ord($m[0]));
-    }, $key);
-
-    return strlen($r) >= $maxLen ? $escape . md5($key) : $r;
+    return CRM_Utils_Cache::cleanKey($key);
   }
 
 }
index ecf107831a48a6e94ab4ae8b4db24b13958db75a..efc723a3fa01e9b7d7f99c6f626e098af700bc37 100644 (file)
@@ -95,7 +95,7 @@ class CRM_Core_BAO_Cache_Psr16 {
           'path' => $path,
         ]);
     }
-    return self::getGroup($group)->get(CRM_Core_BAO_Cache::cleanKey($path));
+    return self::getGroup($group)->get(CRM_Utils_Cache::cleanKey($path));
   }
 
   /**
@@ -138,7 +138,7 @@ class CRM_Core_BAO_Cache_Psr16 {
         ]);
     }
     self::getGroup($group)
-      ->set(CRM_Core_BAO_Cache::cleanKey($path), $data, self::TTL);
+      ->set(CRM_Utils_Cache::cleanKey($path), $data, self::TTL);
   }
 
   /**
@@ -153,7 +153,7 @@ class CRM_Core_BAO_Cache_Psr16 {
     // FIXME: Generate a general deprecation notice.
 
     if ($path) {
-      self::getGroup($group)->delete(CRM_Core_BAO_Cache::cleanKey($path));
+      self::getGroup($group)->delete(CRM_Utils_Cache::cleanKey($path));
     }
     else {
       self::getGroup($group)->clear();
index 416709e59a4fe73774ebe335a9588a293cc00dd7..04c9d5dfd4bc9f89dc86756053f2ccfb1d472601 100644 (file)
@@ -447,7 +447,7 @@ WHERE      c.group_name = %1
 AND        c.created_date < date_sub( NOW( ), INTERVAL %2 day )
 ";
     $params = [
-      1 => [CRM_Core_BAO_Cache::cleanKey('CiviCRM Search PrevNextCache'), 'String'],
+      1 => [CRM_Utils_Cache::cleanKey('CiviCRM Search PrevNextCache'), 'String'],
       2 => [$cacheTimeIntervalDays, 'Integer'],
     ];
     CRM_Core_DAO::executeQuery($sql, $params);
index 5df5e7f2751611d75f881a4876510e3ab900114b..df29c7f3f5154c47aa08fc3310906e04af617b2b 100644 (file)
@@ -530,7 +530,7 @@ class CRM_Core_PseudoConstant {
     $key = 'id',
     $force = NULL
   ) {
-    $cacheKey = CRM_Core_BAO_Cache::cleanKey("CRM_PC_{$name}_{$all}_{$key}_{$retrieve}_{$filter}_{$condition}_{$orderby}");
+    $cacheKey = CRM_Utils_Cache::cleanKey("CRM_PC_{$name}_{$all}_{$key}_{$retrieve}_{$filter}_{$condition}_{$orderby}");
     $cache = CRM_Utils_Cache::singleton();
     $var = $cache->get($cacheKey);
     if ($var !== NULL && empty($force)) {
index 26bdc6c3b7dd76e68fea0cf846868309ea66f36a..93e292127f13af13e1301286579f8c39fd326a1e 100644 (file)
@@ -183,7 +183,7 @@ class CRM_Utils_Cache {
     $types = (array) $params['type'];
 
     if (!empty($params['name'])) {
-      $params['name'] = CRM_Core_BAO_Cache::cleanKey($params['name']);
+      $params['name'] = self::cleanKey($params['name']);
     }
 
     foreach ($types as $type) {
@@ -220,6 +220,37 @@ class CRM_Utils_Cache {
     throw new CRM_Core_Exception("Failed to instantiate cache. No supported cache type found. " . print_r($params, 1));
   }
 
+  /**
+   * Normalize a cache key.
+   *
+   * This bridges an impedance mismatch between our traditional caching
+   * and PSR-16 -- PSR-16 accepts a narrower range of cache keys.
+   *
+   * @param string $key
+   *   Ex: 'ab/cd:ef'
+   * @return string
+   *   Ex: '_abcd1234abcd1234' or 'ab_xx/cd_xxef'.
+   *   A similar key, but suitable for use with PSR-16-compliant cache providers.
+   */
+  public static function cleanKey($key) {
+    if (!is_string($key) && !is_int($key)) {
+      throw new \RuntimeException("Malformed cache key");
+    }
+
+    $maxLen = 64;
+    $escape = '-';
+
+    if (strlen($key) >= $maxLen) {
+      return $escape . md5($key);
+    }
+
+    $r = preg_replace_callback(';[^A-Za-z0-9_\.];', function($m) use ($escape) {
+      return $escape . dechex(ord($m[0]));
+    }, $key);
+
+    return strlen($r) >= $maxLen ? $escape . md5($key) : $r;
+  }
+
   /**
    * Assert that a key is well-formed.
    *
index fe34b5290cad593670e6df5258de8ad66c297553..840886c65edca956eb8c5bf66cdcb2cdfda1a535 100644 (file)
@@ -215,7 +215,7 @@ class CRM_Utils_Cache_SqlGroup implements CRM_Utils_Cache_Interface {
     CRM_Utils_Cache::assertValidKey($key);
     // If we are triggering a deletion of a prevNextCache key in the civicrm_cache tabl
     // Alssure that the relevant prev_next_cache values are also removed.
-    if ($this->group == CRM_Core_BAO_Cache::cleanKey('CiviCRM Search PrevNextCache')) {
+    if ($this->group == CRM_Utils_Cache::cleanKey('CiviCRM Search PrevNextCache')) {
       Civi::service('prevnext')->deleteItem(NULL, $key);
     }
     CRM_Core_DAO::executeQuery("DELETE FROM {$this->table} WHERE {$this->where($key)}");
@@ -225,7 +225,7 @@ class CRM_Utils_Cache_SqlGroup implements CRM_Utils_Cache_Interface {
   }
 
   public function flush() {
-    if ($this->group == CRM_Core_BAO_Cache::cleanKey('CiviCRM Search PrevNextCache') &&
+    if ($this->group == CRM_Utils_Cache::cleanKey('CiviCRM Search PrevNextCache') &&
       Civi::service('prevnext') instanceof CRM_Core_PrevNextCache_Sql) {
       // Use the standard PrevNextCache cleanup function here not just delete from civicrm_cache
       CRM_Core_BAO_PrevNextCache::cleanupCache();
index 99abb9e6493ff1727c1d2c465146dbce87be3e04..537556c0e041390207d4844e6f4a752f6dadaf0a 100644 (file)
@@ -109,7 +109,7 @@ class CRM_Core_BAO_CacheTest extends CiviUnitTestCase {
    * @dataProvider getCleanKeyExamples
    */
   public function testCleanKeys($inputKey, $expectKey) {
-    $actualKey = CRM_Core_BAO_Cache::cleanKey($inputKey);
+    $actualKey = CRM_Utils_Cache::cleanKey($inputKey);
     $this->assertEquals($expectKey, $actualKey);
   }