(REF) Extract helper PhpStormMetadata
authorTim Otten <totten@civicrm.org>
Fri, 25 Aug 2023 22:52:00 +0000 (15:52 -0700)
committerTim Otten <totten@civicrm.org>
Fri, 25 Aug 2023 23:35:58 +0000 (16:35 -0700)
tools/extensions/phpstorm/Civi/PhpStorm/PhpStormCompilePass.php
tools/extensions/phpstorm/Civi/PhpStorm/PhpStormMetadata.php [new file with mode: 0644]
tools/extensions/phpstorm/phpstorm.php

index 8d108c150d6dfe0291d13be8170d8e0ef8065ee5..debe9f8d5e593704f8e0cd2778c6892902ff4fa4 100644 (file)
@@ -7,8 +7,6 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
 
 /**
  * Generate metadata about dynamic CiviCRM services for use by PhpStorm.
- *
- * @link https://www.jetbrains.com/help/phpstorm/2021.3/ide-advanced-metadata.html
  */
 class PhpStormCompilePass implements CompilerPassInterface {
 
@@ -21,17 +19,12 @@ class PhpStormCompilePass implements CompilerPassInterface {
       return;
     }
 
-    // Not 100% sure which is better. These trade-off in edge-cases of writability and readability.
-    //  - '[civicrm.files]/.phpstorm.meta.php'
-    //  - '[civicrm.compile]/.phpstorm.meta.php'
-    //  - '[civicrm.root]/.phpstorm.meta.php'
-    $file = \Civi::paths()->getPath('[civicrm.files]/.phpstorm.meta.php');
-
-    $data = static::renderMetadata(static::findServices($container));
-    file_put_contents($file, $data);
+    $builder = new PhpStormMetadata('services', __CLASS__);
+    $builder->addOverrideMap('\Civi::service()', $this->findServices($container));
+    $builder->write();
   }
 
-  private static function findServices(ContainerBuilder $c): array {
+  private function findServices(ContainerBuilder $c): array {
     $aliases = $c->getAliases();
     $services = [];
     foreach ($c->getServiceIds() as $serviceId) {
@@ -50,20 +43,4 @@ class PhpStormCompilePass implements CompilerPassInterface {
     return $services;
   }
 
-  private static function renderMetadata(array $services): string {
-    ob_start();
-    try {
-      printf('<' . "?php\n");
-      printf("namespace PHPSTORM_META {\n");
-      printf("override(\Civi::service(), map(\n");
-      echo str_replace('\\\\', '\\', var_export($services, 1));
-      // PhpStorm 2022.3.1: 'Civi\\Foo' doesn't work, but 'Civi\Foo' does.
-      printf(");\n");
-      printf("}\n");
-    }
-    finally {
-      return ob_get_clean();
-    }
-  }
-
 }
diff --git a/tools/extensions/phpstorm/Civi/PhpStorm/PhpStormMetadata.php b/tools/extensions/phpstorm/Civi/PhpStorm/PhpStormMetadata.php
new file mode 100644 (file)
index 0000000..854e864
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+
+namespace Civi\PhpStorm;
+
+/**
+ * Utility class for building new metadata files. For example:
+ *
+ * $builder = new PhpStormMetadata('foobar', __CLASS__);
+ * $builder->addOverrideMap('\Foo::bar()', ['gee' => '\Whiz\Bang']);
+ * $builder->write();
+ *
+ * @link https://www.jetbrains.com/help/phpstorm/2021.3/ide-advanced-metadata.html
+ */
+class PhpStormMetadata {
+
+  /**
+   * Short symbolic name. Maps to filename.
+   *
+   * @var string
+   */
+  protected $name;
+
+  /**
+   * The party responsible for building this thing.
+   *
+   * @var string
+   */
+  protected $attribution;
+
+  /**
+   * @var string
+   */
+  protected $buffer;
+
+  /**
+   * @param string $name
+   *   Short symbolic name. This will be used in the filename.
+   * @param string $attribution
+   *   The party responsible for running this builder. If someone finds buggy, where should they look first?
+   */
+  public function __construct(string $name, string $attribution) {
+    $this->name = $name;
+    $this->attribution = $attribution;
+    $this->buffer = '';
+  }
+
+  /**
+   * @param string $for
+   * @param array $map
+   *   Array(string $parameterId => string $className)
+   *
+   * @return $this
+   */
+  public function addOverrideMap(string $for, array $map) {
+    // PhpStorm 2022.3.1: 'Civi\\Foo' doesn't work, but 'Civi\Foo' does.
+    $mapData = str_replace('\\\\', '\\', var_export($map, 1));
+    $this->buffer .= "override($for, map(\n$mapData));\n";
+    return $this;
+  }
+
+  /**
+   * Write the metadata to its file.
+   *
+   * @return void
+   */
+  public function write(): void {
+    $path = phpstorm_metadata_dir();
+
+    if (file_exists($path) && !is_dir($path)) {
+      unlink($path);
+    }
+    if (!file_exists($path)) {
+      mkdir($path);
+    }
+
+    if (!preg_match(';^[-a-zA-Z0-9]+$;', $this->name)) {
+      throw new \RuntimeException("Malformed name for metadata file");
+    }
+    $file = $path . DIRECTORY_SEPARATOR . $this->name . '.php';
+
+    $lines = [];
+    $lines[] = "<" . "?php";
+    $lines[] = sprintf("// Generated by %s at %s", $this->attribution, date('Y-m-d H:i:s'));
+    $lines[] = "namespace PHPSTORM_META {\n";
+    $lines[] = $this->buffer;
+    $lines[] = "}";
+
+    $content = implode("\n", $lines) . "\n";
+    file_put_contents($file, $content);
+  }
+
+}
index 71a5085f44898d23e0af1090d7a795c4117c58b7..d778d90eaac9e3467b8a8505358c1a730b60980d 100644 (file)
@@ -6,6 +6,20 @@
 // phpcs:enable
 use Symfony\Component\DependencyInjection\Compiler\PassConfig;
 
+/**
+ * Determine the folder where we will store PhpStorm metadata files.
+ *
+ *  Not 100% sure which is best. These options trade-off in edge-cases of writability and readability:
+ *  - '[civicrm.files]/.phpstorm.meta.php'
+ *  - '[civicrm.compile]/.phpstorm.meta.php'
+ *  - '[civicrm.root]/.phpstorm.meta.php'
+ *
+ * @return string
+ */
+function phpstorm_metadata_dir(): string {
+  return \Civi::paths()->getPath('[civicrm.files]/.phpstorm.meta.php');
+}
+
 /**
  * Implements hook_civicrm_config().
  *