-
-
Notifications
You must be signed in to change notification settings - Fork 824
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add civimix-schema with SchemaHelper and AutomaticUpgrader
- Loading branch information
Showing
6 changed files
with
453 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?php | ||
namespace CiviMix\Schema; | ||
|
||
\pathload()->activatePackage('civimix-schema@5', __DIR__, [ | ||
'reloadable' => TRUE, | ||
// The civimix-schema library specifically supports installation processes. From a | ||
// bootstrap/service-availability POV, this is a rough environment which leads to | ||
// the "Multi-Activation Issue" and "Multi-Download Issue". To adapt to them, | ||
// civimix-schema follows "Reloadable Library" patterns. | ||
// More information: https://github.com/totten/pathload-poc/blob/master/doc/issues.md | ||
]); | ||
|
||
// When reloading, we make newer instance of the Facade object. | ||
$GLOBALS['CiviMixSchema5'] = require __DIR__ . '/src/CiviMixSchema5.php'; | ||
|
||
if (!interface_exists(__NAMESPACE__ . '\SchemaHelperInterface')) { | ||
require __DIR__ . '/src/SchemaHelperInterface.php'; | ||
} | ||
|
||
// \CiviMix\Schema\loadClass() is a facade. The facade should remain identical across versions. | ||
if (!function_exists(__NAMESPACE__ . '\loadClass')) { | ||
|
||
function loadClass(string $class) { | ||
return $GLOBALS['CiviMixSchema5']->loadClass($class); | ||
} | ||
|
||
spl_autoload_register(__NAMESPACE__ . '\loadClass'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
<?php | ||
|
||
namespace CiviMix\Schema; | ||
|
||
/** | ||
* The "AutomaticUpgrader" will create and destroy the SQL tables | ||
* using XML schema files (`SchemaHelper`). It also calls-out to any custom | ||
* upgrade code (eg `CRM_Myext_Upgrader`). | ||
* | ||
* To simplify backport considerations, `AutomaticUpgrader` does not have formal name. | ||
* It is accessed via aliases like "CiviMix\Schema\*\AutomaticUpgrader". | ||
* | ||
* Target: CiviCRM v5.38+ | ||
*/ | ||
return new class() implements \CRM_Extension_Upgrader_Interface { | ||
|
||
use \CRM_Extension_Upgrader_IdentityTrait { | ||
|
||
init as initIdentity; | ||
|
||
} | ||
|
||
/** | ||
* Optionally delegate to "CRM_Myext_Upgrader" or "Civi\Myext\Upgrader". | ||
* | ||
* @var \CRM_Extension_Upgrader_Interface|null | ||
*/ | ||
private $customUpgrader; | ||
|
||
public function init(array $params) { | ||
$this->initIdentity($params); | ||
if ($info = $this->getInfo()) { | ||
if ($class = $this->getDelegateUpgraderClass($info)) { | ||
$this->customUpgrader = new $class(); | ||
$this->customUpgrader->init($params); | ||
if ($errors = $this->checkDelegateCompatibility($this->customUpgrader)) { | ||
throw new \CRM_Core_Exception("AutomaticUpgrader is not compatible with $class:\n" . implode("\n", $errors)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
public function notify(string $event, array $params = []) { | ||
$info = $this->getInfo(); | ||
if (!$info) { | ||
return; | ||
} | ||
|
||
if ($event === 'install') { | ||
$GLOBALS['CiviMixSchema5']->getHelper($this->getExtensionKey())->install(); | ||
} | ||
|
||
if ($this->customUpgrader) { | ||
$this->customUpgrader->notify($event, $params); | ||
} | ||
|
||
if ($event === 'uninstall') { | ||
$GLOBALS['CiviMixSchema5']->getHelper($this->getExtensionKey())->uninstall(); | ||
} | ||
} | ||
|
||
/** | ||
* Civix-based extensions have a conventional name for their upgrader class ("CRM_Myext_Upgrader" | ||
* or "Civi\Myext\Upgrader"). Figure out if this class exists. | ||
* | ||
* @param \CRM_Extension_Info $info | ||
* @return string|null | ||
* Ex: 'CRM_Myext_Upgrader' or 'Civi\Myext\Upgrader' | ||
*/ | ||
public function getDelegateUpgraderClass(\CRM_Extension_Info $info): ?string { | ||
$candidates = []; | ||
|
||
if (!empty($info->civix['namespace'])) { | ||
$namespace = $info->civix['namespace']; | ||
$candidates[] = sprintf('%s_Upgrader', str_replace('/', '_', $namespace)); | ||
$candidates[] = sprintf('%s\\Upgrader', str_replace('/', '\\', $namespace)); | ||
} | ||
|
||
foreach ($candidates as $candidate) { | ||
if (class_exists($candidate)) { | ||
return $candidate; | ||
} | ||
} | ||
|
||
return NULL; | ||
} | ||
|
||
public function getInfo(): ?\CRM_Extension_Info { | ||
try { | ||
return \CRM_Extension_System::singleton()->getMapper()->keyToInfo($this->extensionName); | ||
} | ||
catch (\CRM_Extension_Exception_ParseException $e) { | ||
\Civi::log()->error("Parse error in extension " . $this->extensionName . ": " . $e->getMessage()); | ||
return NULL; | ||
} | ||
} | ||
|
||
/** | ||
* @param \CRM_Extension_Upgrader_Interface $upgrader | ||
* @return array | ||
* List of error messages. | ||
*/ | ||
public function checkDelegateCompatibility($upgrader): array { | ||
$class = get_class($upgrader); | ||
|
||
$errors = []; | ||
|
||
if (!($upgrader instanceof \CRM_Extension_Upgrader_Base)) { | ||
$errors[] = "$class is not based on CRM_Extension_Upgrader_Base."; | ||
return $errors; | ||
} | ||
|
||
// In the future, we will probably modify AutomaticUpgrader to build its own | ||
// sequence of revisions (based on other sources of data). AutomaticUpgrader | ||
// is only regarded as compatible with classes that strictly follow the standard revision-model. | ||
$methodNames = [ | ||
'appendTask', | ||
'onUpgrade', | ||
'getRevisions', | ||
'getCurrentRevision', | ||
'setCurrentRevision', | ||
'enqueuePendingRevisions', | ||
'hasPendingRevisions', | ||
]; | ||
foreach ($methodNames as $methodName) { | ||
$method = new \ReflectionMethod($upgrader, $methodName); | ||
if ($method->getDeclaringClass()->getName() !== 'CRM_Extension_Upgrader_Base') { | ||
$errors[] = "To ensure future interoperability, AutomaticUpgrader only supports {$class}::{$methodName}() if it's inherited from CRM_Extension_Upgrader_Base"; | ||
} | ||
} | ||
|
||
return $errors; | ||
} | ||
|
||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?php | ||
namespace CiviMix\Schema; | ||
|
||
/** | ||
* This object is known as $GLOBALS['CiviMixSchema5']. It is a reloadable service-object. | ||
* (It may be reloaded if you enable a new extension that includes an upgraded copy.) | ||
*/ | ||
return new class() { | ||
|
||
/** | ||
* @var string | ||
* Regular expression. Note the 2 groupings. $m[1] identifies a per-extension namespace. $m[2] identifies the actual class. | ||
*/ | ||
private $regex = ';^CiviMix\\\Schema\\\(\w+)\\\(AutomaticUpgrader)$;'; | ||
|
||
/** | ||
* If someone requests a class like: | ||
* | ||
* CiviMix\Schema\MyExt\AutomaticUpgrader | ||
* | ||
* then load the latest version of: | ||
* | ||
* civimix-schema/src/Helper.php | ||
*/ | ||
public function loadClass(string $class) { | ||
if (preg_match($this->regex, $class, $m)) { | ||
$absPath = __DIR__ . DIRECTORY_SEPARATOR . $m[2] . '.php'; | ||
class_alias(get_class(require $absPath), $class); | ||
} | ||
} | ||
|
||
/** | ||
* @param string $extensionKey | ||
* Ex: 'org.civicrm.flexmailer' | ||
* @return \CiviMix\Schema\SchemaHelperInterface | ||
*/ | ||
public function getHelper(string $extensionKey) { | ||
$store = &\Civi::$statics['CiviMixSchema5-helpers']; | ||
if (!isset($store[$extensionKey])) { | ||
$class = get_class(require __DIR__ . '/SchemaHelper.php'); | ||
$store[$extensionKey] = new $class($extensionKey); | ||
} | ||
return $store[$extensionKey]; | ||
} | ||
|
||
}; |
Oops, something went wrong.