Unverified Commit 6fbf27cc authored by Mateu Aguiló Bosch's avatar Mateu Aguiló Bosch Committed by Mateu Aguiló Bosch
Browse files

Issue #3296304 by e0ipso: Support PHP extensibility for components

parent 95c36626
Loading
Loading
Loading
Loading
+12 −5
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@

use Drupal\cl_components\Component\Component;
use Drupal\cl_components\Component\ComponentDiscovery;
use Drupal\cl_components\Exception\InvalidComponentHookException;

/**
 * @file
@@ -18,16 +19,22 @@ use Drupal\cl_components\Component\ComponentDiscovery;
 */
function cl_components_library_info_build() {
  // Iterate over the components' directory to find all the components.
  $component_repository = \Drupal::service(ComponentDiscovery::class);
  assert($component_repository instanceof ComponentDiscovery);
  $components = $component_repository->findAll();
  $discovery = \Drupal::service(ComponentDiscovery::class);
  assert($discovery instanceof ComponentDiscovery);
  $components = $discovery->findAll();
  $libraries = array_reduce(
    $components,
    static function (array $libraries, Component $component) use ($component_repository) {
      $library = $component_repository->libraryFromComponent($component);
    static function (array $libraries, Component $component) use ($discovery) {
      $library = $discovery->libraryFromComponent($component);
      if (empty($library)) {
        return $libraries;
      }
      try {
        $library = $component->invokeHook('library_info_alter', [$library]);
      }
      catch (InvalidComponentHookException $e) {
        // The component does not support this hook. It is fine, do nothing.
      }
      return array_merge($libraries, [$component->getId() => $library]);
    },
    []
+32 −0
Original line number Diff line number Diff line
<?php

/**
 * @file
 * Component PHP integration.
 */

use Drupal\cl_components\Component\Component;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;

/**
 * Implements cl_component_COMPONENT_library_info_alter().
 */
function cl_component_my_button_library_info_alter($library, Component $component): array {
  // Ensure all JS for the button is deferred.
  $library['js'] = array_map(
    static fn(array $value) => NestedArray::mergeDeep($value, ['attributes' => ['defer' => TRUE]]),
    $library['js'] ?? []
  );
  return $library;
}

/**
 * Implements cl_component_COMPONENT_form_alter().
 */
function cl_component_my_button_form_alter($form, FormStateInterface $form_state, Component $component): array {
  $form['data']['iconType']['#description'] = \t('The component type will determine the icon in the button.');
  $form['data']['iconType']['#default_value'] = $form['data']['iconType']['#default_value'] ?? 'power';
  $form['data']['text']['#placeholder'] = \t('Click me!');
  return $form;
}
+27 −0
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
namespace Drupal\cl_components\Component;

use Drupal\cl_components\Exception\InvalidComponentException;
use Drupal\cl_components\Exception\InvalidComponentHookException;
use Drupal\cl_components\Exception\TemplateNotFoundException;
use Drupal\Component\Utility\Html;
use Drupal\Core\StringTranslation\StringTranslationTrait;
@@ -213,6 +214,32 @@ class Component {
    return $this->metadata;
  }

  /**
   * Invokes a CL Component hook.
   *
   * @param string $hook
   *   The hook to invoke.
   * @param array $args
   *   The arguments for the hook.
   *
   * @return mixed
   *   The value returned by the component hook.
   *
   * @throws \Drupal\cl_components\Exception\InvalidComponentHookException
   */
  public function invokeHook(string $hook, array $args): mixed {
    $metadata = $this->getMetadata();
    if (!in_array($hook, $metadata->getHooks())) {
      $message = sprintf('The requested hook "%s" is not supported by component "%s".', $hook, $this->getId());
      throw new InvalidComponentHookException($message);
    }
    $func_name = sprintf('cl_component_%s_%s', $metadata->getSafeName(), $hook);
    if (!function_exists($func_name)) {
      require_once $this->getMetadata()->getPhpPath();
    }
    return $func_name(...[...$args, $this]);
  }

  /**
   * Calculates additional context for this template.
   *
+59 −4
Original line number Diff line number Diff line
@@ -3,9 +3,9 @@
namespace Drupal\cl_components\Component;

use Drupal\cl_components\Exception\InvalidComponentException;
use Drupal\cl_components\Parser\ComponentPhpFile;
use Drupal\Component\Serialization\Json;
use Drupal\Core\File\FileUrlGeneratorInterface;
use Drupal\Core\Url;
use JsonSchema\Validator;

/**
@@ -121,6 +121,27 @@ final class ComponentMetadata {
   */
  private string $description;

  /**
   * The hook names this component implements.
   *
   * @var string[]
   */
  private array $hooks = [];

  /**
   * The name safe for using in PHP functions.
   *
   * @var string
   */
  private string $safeName;

  /**
   * The full path to the PHP file.
   *
   * @var string
   */
  private string $phpPath;

  /**
   * ComponentMetadata constructor.
   *
@@ -169,6 +190,13 @@ final class ComponentMetadata {

    // Save the schemas.
    $this->parseSchemaInfo($metadata_info);
    // Parse the hooks.
    $this->safeName = preg_replace('@[^a-z0-9]+@', '_', strtolower($this->machineName));
    $php_file = new ComponentPhpFile($this->path, $this->machineName, $this->safeName);
    $this->phpPath = $php_file->path;
    if ($php_file->hasPhpFile()) {
      $this->hooks = $php_file->parseHooks();
    }
  }

  /**
@@ -227,9 +255,7 @@ final class ComponentMetadata {
      if (!is_array($type)) {
        $type = [$type];
      }
      $type = array_merge($type, ['object']);
      $type = array_unique($type);
      $schema['type'] = $type;
      $schema['type'] = array_unique([...$type, 'object']);
      $this->schemas['props']['properties'][$name]['type'] = $type;
    }
    $this->schemas['named_blocks'] = $this->parseNamedBlockSchemaInfo();
@@ -465,4 +491,33 @@ final class ComponentMetadata {
    return $this->description;
  }

  /**
   * Gets the machine name safe to use in PHP function names.
   *
   * @return string
   */
  public function getSafeName(): string {
    return $this->safeName;
  }

  /**
   * Gets the hook names.
   *
   * @return string[]
   *   The hook names.
   */
  public function getHooks(): array {
    return $this->hooks;
  }

  /**
   * The path to the PHP file.
   *
   * @return string
   *   The path to the PHP file.
   */
  public function getPhpPath(): string {
    return $this->phpPath;
  }

}
+27 −4
Original line number Diff line number Diff line
@@ -166,10 +166,31 @@ final class ComponentAudit extends ControllerBase {
    $assets = $this->discovery->discoverDistAssets($path);
    $assets_message = [
      '#theme' => 'item_list',
      '#items' => array_map(static fn(string $file) => preg_replace('@.*/' . $component->getId() . '/@', '', $file), [
      '#items' => array_map(
        static fn(string $file) => [
          '#type' => 'html_tag',
          '#tag' => 'code',
          '#value' => preg_replace('@.*/' . $component->getId() . '/@', '', $file),
        ],
        [
          ...$assets['js'] ?? [],
          ...$assets['css'] ?? [],
      ]),
        ]
      ),
    ];
    $hooks = $metadata->getHooks();
    $hooks_message = empty($hooks)
      ? [
        '#type' => 'html_tag',
        '#tag' => 'em',
        '#value' => $this->t('No hooks in <code>@filename</code>.', ['@filename' => $component->getId() . '.php']),
      ]
      : [
      '#theme' => 'item_list',
      '#items' => array_map(
        static fn(string $hook) => ['#type' => 'html_tag', '#tag' => 'code', '#value' => $hook],
        $hooks
      ),
    ];
    $card_build = [
      'title' => [
@@ -195,6 +216,7 @@ final class ComponentAudit extends ControllerBase {
          $this->t('Default Template'),
          $this->t('Variants'),
          $this->t('Assets'),
          $this->t('Implemented Hooks'),
        ],
        '#rows' => [
          [
@@ -202,6 +224,7 @@ final class ComponentAudit extends ControllerBase {
            $template_message,
            $variants_message,
            ['data' => $assets_message],
            ['data' => $hooks_message],
          ],
        ],
      ],
Loading