diff core/lib/Drupal/Component/ProxyBuilder/ProxyBuilder.php @ 0:c75dbcec494b

Initial commit from drush-created site
author Chris Cannam
date Thu, 05 Jul 2018 14:24:15 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/lib/Drupal/Component/ProxyBuilder/ProxyBuilder.php	Thu Jul 05 14:24:15 2018 +0000
@@ -0,0 +1,352 @@
+<?php
+
+namespace Drupal\Component\ProxyBuilder;
+
+/**
+ * Generates the string representation of the proxy service.
+ */
+class ProxyBuilder {
+
+  /**
+   * Generates the used proxy class name from a given class name.
+   *
+   * @param string $class_name
+   *   The class name of the actual service.
+   *
+   * @return string
+   *   The class name of the proxy.
+   */
+  public static function buildProxyClassName($class_name) {
+    $match = [];
+    preg_match('/([a-zA-Z0-9_]+\\\\[a-zA-Z0-9_]+)\\\\(.+)/', $class_name, $match);
+    $root_namespace = $match[1];
+    $rest_fqcn = $match[2];
+    $proxy_class_name = $root_namespace . '\\ProxyClass\\' . $rest_fqcn;
+
+    return $proxy_class_name;
+  }
+
+  /**
+   * Generates the used proxy namespace from a given class name.
+   *
+   * @param string $class_name
+   *   The class name of the actual service.
+   *
+   * @return string
+   *   The namespace name of the proxy.
+   */
+  public static function buildProxyNamespace($class_name) {
+    $proxy_classname = static::buildProxyClassName($class_name);
+
+    preg_match('/(.+)\\\\[a-zA-Z0-9]+/', $proxy_classname, $match);
+    $proxy_namespace = $match[1];
+    return $proxy_namespace;
+  }
+
+  /**
+   * Builds a proxy class string.
+   *
+   * @param string $class_name
+   *   The class name of the actual service.
+   * @param string $proxy_class_name
+   *   (optional) The class name of the proxy service.
+   *
+   * @return string
+   *   The full string with namespace class and methods.
+   */
+  public function build($class_name, $proxy_class_name = '') {
+    $reflection = new \ReflectionClass($class_name);
+
+    if ($proxy_class_name) {
+      $proxy_class_reflection = new \ReflectionClass($proxy_class_name);
+      $proxy_namespace = $proxy_class_reflection->getNamespaceName();
+    }
+    else {
+      $proxy_class_name = $this->buildProxyClassName($class_name);
+      $proxy_namespace = $this->buildProxyNamespace($class_name);
+      $proxy_class_shortname = str_replace($proxy_namespace . '\\', '', $proxy_class_name);
+    }
+
+    $output = '';
+    $class_documentation = <<<'EOS'
+
+namespace {{ namespace }}{
+
+    /**
+     * Provides a proxy class for \{{ class_name }}.
+     *
+     * @see \Drupal\Component\ProxyBuilder
+     */
+
+EOS;
+    $class_start = '    class {{ proxy_class_shortname }}';
+
+    // For cases in which the implemented interface is a child of another
+    // interface, getInterfaceNames() also returns the parent. This causes a
+    // PHP error.
+    // In order to avoid that, check for each interface, whether one of its
+    // parents is also in the list and exclude it.
+    if ($interfaces = $reflection->getInterfaces()) {
+      foreach ($interfaces as $interface_name => $interface) {
+        // Exclude all parents from the list of implemented interfaces of the
+        // class.
+        if ($parent_interfaces = $interface->getInterfaceNames()) {
+          foreach ($parent_interfaces as $parent_interface) {
+            unset($interfaces[$parent_interface]);
+          }
+        }
+      }
+
+      $interface_names = [];
+      foreach ($interfaces as $interface) {
+        $interface_names[] = '\\' . $interface->getName();
+      }
+      $class_start .= ' implements ' . implode(', ', $interface_names);
+    }
+
+    $output .= $this->buildUseStatements();
+
+    // The actual class;
+    $properties = <<<'EOS'
+/**
+ * The id of the original proxied service.
+ *
+ * @var string
+ */
+protected $drupalProxyOriginalServiceId;
+
+/**
+ * The real proxied service, after it was lazy loaded.
+ *
+ * @var \{{ class_name }}
+ */
+protected $service;
+
+/**
+ * The service container.
+ *
+ * @var \Symfony\Component\DependencyInjection\ContainerInterface
+ */
+protected $container;
+
+
+EOS;
+
+    $output .= $properties;
+
+    // Add all the methods.
+    $methods = [];
+    $methods[] = $this->buildConstructorMethod();
+    $methods[] = $this->buildLazyLoadItselfMethod();
+
+    // Add all the methods of the proxied service.
+    $reflection_methods = $reflection->getMethods();
+
+    foreach ($reflection_methods as $method) {
+      if ($method->getName() === '__construct') {
+        continue;
+      }
+
+      if ($method->isPublic()) {
+        $methods[] = $this->buildMethod($method) . "\n";
+      }
+    }
+
+    $output .= implode("\n", $methods);
+
+    // Indent the output.
+    $output = implode("\n", array_map(function ($value) {
+      if ($value === '') {
+        return $value;
+      }
+      return "        $value";
+    }, explode("\n", $output)));
+
+    $final_output = $class_documentation . $class_start . "\n    {\n\n" . $output . "\n    }\n\n}\n";
+
+    $final_output = str_replace('{{ class_name }}', $class_name, $final_output);
+    $final_output = str_replace('{{ namespace }}', $proxy_namespace ? $proxy_namespace . ' ' : '', $final_output);
+    $final_output = str_replace('{{ proxy_class_shortname }}', $proxy_class_shortname, $final_output);
+
+    return $final_output;
+  }
+
+  /**
+   * Generates the string for the method which loads the actual service.
+   *
+   * @return string
+   */
+  protected function buildLazyLoadItselfMethod() {
+    $output = <<<'EOS'
+/**
+ * Lazy loads the real service from the container.
+ *
+ * @return object
+ *   Returns the constructed real service.
+ */
+protected function lazyLoadItself()
+{
+    if (!isset($this->service)) {
+        $this->service = $this->container->get($this->drupalProxyOriginalServiceId);
+    }
+
+    return $this->service;
+}
+
+EOS;
+
+    return $output;
+  }
+
+  /**
+   * Generates the string representation of a single method: signature, body.
+   *
+   * @param \ReflectionMethod $reflection_method
+   *   A reflection method for the method.
+   *
+   * @return string
+   */
+  protected function buildMethod(\ReflectionMethod $reflection_method) {
+
+    $parameters = [];
+    foreach ($reflection_method->getParameters() as $parameter) {
+      $parameters[] = $this->buildParameter($parameter);
+    }
+
+    $function_name = $reflection_method->getName();
+
+    $reference = '';
+    if ($reflection_method->returnsReference()) {
+      $reference = '&';
+    }
+
+    $signature_line = <<<'EOS'
+/**
+ * {@inheritdoc}
+ */
+
+EOS;
+
+    if ($reflection_method->isStatic()) {
+      $signature_line .= 'public static function ' . $reference . $function_name . '(';
+    }
+    else {
+      $signature_line .= 'public function ' . $reference . $function_name . '(';
+    }
+
+    $signature_line .= implode(', ', $parameters);
+    $signature_line .= ')';
+
+    $output = $signature_line . "\n{\n";
+
+    $output .= $this->buildMethodBody($reflection_method);
+
+    $output .= "\n" . '}';
+    return $output;
+  }
+
+  /**
+   * Builds a string for a single parameter of a method.
+   *
+   * @param \ReflectionParameter $parameter
+   *   A reflection object of the parameter.
+   *
+   * @return string
+   */
+  protected function buildParameter(\ReflectionParameter $parameter) {
+    $parameter_string = '';
+
+    if ($parameter->isArray()) {
+      $parameter_string .= 'array ';
+    }
+    elseif ($parameter->isCallable()) {
+      $parameter_string .= 'callable ';
+    }
+    elseif ($class = $parameter->getClass()) {
+      $parameter_string .= '\\' . $class->getName() . ' ';
+    }
+
+    if ($parameter->isPassedByReference()) {
+      $parameter_string .= '&';
+    }
+
+    $parameter_string .= '$' . $parameter->getName();
+
+    if ($parameter->isDefaultValueAvailable()) {
+      $parameter_string .= ' = ';
+      $parameter_string .= var_export($parameter->getDefaultValue(), TRUE);
+    }
+
+    return $parameter_string;
+  }
+
+  /**
+   * Builds the body of a wrapped method.
+   *
+   * @param \ReflectionMethod $reflection_method
+   *   A reflection method for the method.
+   *
+   * @return string
+   */
+  protected function buildMethodBody(\ReflectionMethod $reflection_method) {
+    $output = '';
+
+    $function_name = $reflection_method->getName();
+
+    if (!$reflection_method->isStatic()) {
+      $output .= '    return $this->lazyLoadItself()->' . $function_name . '(';
+    }
+    else {
+      $class_name = $reflection_method->getDeclaringClass()->getName();
+      $output .= "    \\$class_name::$function_name(";
+    }
+
+    // Add parameters;
+    $parameters = [];
+    foreach ($reflection_method->getParameters() as $parameter) {
+      $parameters[] = '$' . $parameter->getName();
+    }
+
+    $output .= implode(', ', $parameters) . ');';
+
+    return $output;
+  }
+
+  /**
+   * Builds the constructor used to inject the actual service ID.
+   *
+   * @return string
+   */
+  protected function buildConstructorMethod() {
+    $output = <<<'EOS'
+/**
+ * Constructs a ProxyClass Drupal proxy object.
+ *
+ * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+ *   The container.
+ * @param string $drupal_proxy_original_service_id
+ *   The service ID of the original service.
+ */
+public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container, $drupal_proxy_original_service_id)
+{
+    $this->container = $container;
+    $this->drupalProxyOriginalServiceId = $drupal_proxy_original_service_id;
+}
+
+EOS;
+
+    return $output;
+  }
+
+  /**
+   * Build the required use statements of the proxy class.
+   *
+   * @return string
+   */
+  protected function buildUseStatements() {
+    $output = '';
+
+    return $output;
+  }
+
+}