info.xml - Allow PSR-0 style class-loader declarations
authorTim Otten <totten@civicrm.org>
Mon, 22 Mar 2021 23:40:39 +0000 (16:40 -0700)
committerTim Otten <totten@civicrm.org>
Mon, 22 Mar 2021 23:58:10 +0000 (16:58 -0700)
Before
------

If an extension defines a class in the style of `CRM_Foo_Bar`, then it
*must* use `hook_civicrm_config` and modify the `include_path`.

```php
function mymod_civicrm_config(&$config = NULL) {
  $extRoot = dirname(__FILE__) . DIRECTORY_SEPARATOR;
  $include_path = $extRoot . PATH_SEPARATOR . get_include_path();
  set_include_path($include_path);
}
```

Consequently:

* The `include_path` gets rather long.
* Setting up the extension class-loader is not sufficient for loading these classes. You must
  wait until the DB has initialized and the `hook_civicrm_config` has fired.

After
-----

An extension *may* setup classloading in `info.xml`, e.g.

```xml
<classloader>
  <psr0 prefix="CRM_" path="" />
</classloader>
```

If the `<psr0>` style is used, then it is possible to scan the extension
classes without fully booting the extension.  This is a pre-requisite for
using interfaces, annotations, or similar for event-registration.

Comments
--------

* This style closely parallels the `<psr4>` support in `info.xml`, and it parallels the semantics of `composer.json`.
* It is still valid for an extension to set `include_path` -- that works the same as ever.

CRM/Extension/ClassLoader.php
CRM/Extension/Info.php
tests/phpunit/CRM/Extension/InfoTest.php

index 2e181a887784b13be254a32e4e665329bbb62da6..5b0f30725e6fa868fdc22ea60f98c1ea9b540cce 100644 (file)
  */
 class CRM_Extension_ClassLoader {
 
+  /**
+   * List of class-loader features that are valid in this version of Civi.
+   *
+   * This may be useful for some extensions which enable/disable polyfills based on environment.
+   */
+  const FEATURES = ',psr0,psr4,';
+
   /**
    * @var CRM_Extension_Mapper
    */
@@ -92,6 +99,10 @@ class CRM_Extension_ClassLoader {
       if (!empty($info->classloader)) {
         foreach ($info->classloader as $mapping) {
           switch ($mapping['type']) {
+            case 'psr0':
+              $loader->add($mapping['prefix'], CRM_Utils_File::addTrailingSlash($path . '/' . $mapping['path']));
+              break;
+
             case 'psr4':
               $loader->addPsr4($mapping['prefix'], $path . '/' . $mapping['path']);
               break;
index 7a0683be43bf6c5539851dbc51269ca741b4e378..67a58af265e9a1155353f3f68e4dd926bf34c0b2 100644 (file)
@@ -175,6 +175,13 @@ class CRM_Extension_Info {
             'path' => (string) $psr4->attributes()->path,
           ];
         }
+        foreach ($val->psr0 as $psr0) {
+          $this->classloader[] = [
+            'type' => 'psr0',
+            'prefix' => (string) $psr0->attributes()->prefix,
+            'path' => (string) $psr0->attributes()->path,
+          ];
+        }
       }
       elseif ($attr === 'tags') {
         $this->tags = [];
index 77b46076aca5b5a13dcb8c87e0239b481476e283..c016abd51c50709ca30dd6f0af18957a1684f793 100644 (file)
@@ -55,7 +55,10 @@ class CRM_Extension_InfoTest extends CiviUnitTestCase {
 
   public function testGood_string_extras() {
     $data = "<extension key='test.bar' type='module'><file>testbar</file>
-      <classloader><psr4 prefix=\"Civi\\\" path=\"Civi\"/></classloader>
+      <classloader>
+        <psr4 prefix=\"Civi\\\" path=\"Civi\"/>
+        <psr0 prefix=\"CRM_\" path=\"\"/>
+      </classloader>
       <requires><ext>org.civicrm.a</ext><ext>org.civicrm.b</ext></requires>
     </extension>
     ";
@@ -65,6 +68,10 @@ class CRM_Extension_InfoTest extends CiviUnitTestCase {
     $this->assertEquals('testbar', $info->file);
     $this->assertEquals('Civi\\', $info->classloader[0]['prefix']);
     $this->assertEquals('Civi', $info->classloader[0]['path']);
+    $this->assertEquals('psr4', $info->classloader[0]['type']);
+    $this->assertEquals('CRM_', $info->classloader[1]['prefix']);
+    $this->assertEquals('', $info->classloader[1]['path']);
+    $this->assertEquals('psr0', $info->classloader[1]['type']);
     $this->assertEquals(['org.civicrm.a', 'org.civicrm.b'], $info->requires);
   }